Compare commits

..

9 Commits

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

5
.gitignore vendored
View File

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

View File

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

View File

@ -1,12 +1,10 @@
# Spacedeck Open
![Spacedeck 6.0 Screenshot](/public/images/sd6-screenshot.png)
This is the free and open source version of Spacedeck, a web based, real time, collaborative whiteboard application with rich media support. Spacedeck was developed in 6 major releases during Autumn 2011 until the end of 2016 and was originally a commercial SaaS. The developers were Lukas F. Hartmann (mntmn) and Martin GĂĽther (magegu). All icons and large parts of the CSS were designed by Thomas Helbig (dergraph).
This is the free and open source version of Spacedeck, a web based, real time, collaborative whiteboard application with rich media support. Spacedeck was developed in 6 major releases during Autumn 2011 until the end of 2016 and was originally a commercial SaaS. The developers were Lukas F. Hartmann (mntmn) and Martin GĂĽther (magegu).
As we plan to retire the subscription based service at spacedeck.com in May 2018, we decided to open-source Spacedeck to allow educational and other organizations who currently rely on Spacedeck to migrate to a self-hosted or local version.
The spacedeck.com online service was shut down on May 1st 2018. We decided to open-source Spacedeck to allow educational and other organizations who currently rely on Spacedeck to migrate to a self-hosted or local version.
[MNT Research GmbH](https://mntre.com) has restarted development of Spacedeck Open in 2020.
Data migration features will be added soon.
We appreciate filed issues, pull requests and general discussion.
@ -17,28 +15,36 @@ We appreciate filed issues, pull requests and general discussion.
- Write and format text with full control over fonts, colors and style
- Draw, annotate and highlight with included graphical shapes
- Turn your Space into a zooming presentation
- Collaborate in realtime with teammates, students or friends
- Collaborate and chat in realtime with teammates, students or friends
- Share Spaces on the web or via email
- Export your work as printable PDF or ZIP (currently being fixed, stay tuned)
# Use Cases
- Education: Virtual classwork with multimedia
- Creative: Mood boards, Brainstorming, Design Thinking
- Visual note taking and planning
- Export your work as printable PDF or ZIP
# Requirements, Installation
Spacedeck requires:
Spacedeck uses the following major building blocks:
- Node.js 10.x: Web Server / API. Download: https://nodejs.org
- Node.js 9.x: Web Server / API
- Vue.js: Frontend UI Framework (included)
- SQLite (included)
To run Spacedeck, you only need Node.JS 10.x.
It also has some optional binary dependencies for advanced media conversion:
To install all node dependencies, run (do this once):
- ffmpeg and ffprobe (for video/audio conversion)
- audiowaveform (for audio waveform rendering) (https://github.com/bbcrd/audiowaveform)
- ghostscript (gs, for PDF import)
By default, media files are uploaded to the ```storage``` folder.
To use Spacedeck, you only need Node.JS 9.x.
Then, to install all node dependencies, run
npm install
To rebuild the frontend CSS styles (you need to do this at least once):
gulp styles
# Configuration
See [config/default.json](config/default.json)
@ -49,24 +55,9 @@ See [config/default.json](config/default.json)
Then open http://localhost:9666 in a web browser.
# Optional Dependencies
# Run (desktop app with integrated web server)
For advanced media conversion:
- ffmpeg and ffprobe for video/audio conversion. Download: https://www.ffmpeg.org/download.html
- audiowaveform for audio waveform rendering. Download: https://github.com/bbcrd/audiowaveform
- ghostscript for PDF import. Download: https://www.ghostscript.com/download/gsdnld.html
# Data Storage
By default, media files are uploaded to the ```storage``` folder.
The database is stored in ```database.sqlite``` by default.
# Hacking
To rebuild the frontend CSS styles:
gulp styles
electron .
# License

0
bin/www Normal file → Executable file
View File

View File

@ -1,11 +1,8 @@
{
"team_name": "My Open Spacedeck",
"contact_email": "support@example.org",
//"endpoint": "http://localhost:9000",
"endpoint": "http://localhost:9666",
"invite_code": "", //disabled invite code by default
"storage_region": "eu-central-1",
//"storage_bucket": "sdeck-development",
//"storage_cdn": "http://localhost:9123/sdeck-development",
//"storage_endpoint": "http://storage:9000",
@ -21,14 +18,5 @@
"google_access" : "",
"google_secret" : "",
"admin_pass": "very_secret_admin_password",
"phantom_api_secret": "very_secret_phantom_password",
// Choose "console" or "smtp"
"mail_provider": "smtp",
"mail_smtp_host": "your.smtp.host",
"mail_smtp_port": 465,
"mail_smtp_secure": true,
"mail_smtp_require_tls": true,
"mail_smtp_user": "your.smtp.user",
"mail_smtp_pass": "your.secret.smtp.password"
"phantom_api_secret": "very_secret_phantom_password"
}

View File

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

View File

@ -37,7 +37,7 @@ const convertableAudioTypes = [
"application/ogg",
"audio/amr",
"audio/3ga",
"audio/wave",
"audio/wav",
"audio/3gpp",
"audio/x-wav",
"audio/aiff",
@ -92,14 +92,14 @@ function createWaveform(fileName, localFilePath, callback){
});
}
function convertVideo(fileName, filePath, codec, callback, progressCallback) {
function convertVideo(fileName, filePath, codec, callback, progress_callback) {
var ext = path.extname(fileName);
var presetMime = mime.lookup(fileName);
var newExt = codec == "mp4" ? "mp4" : "ogv";
var convertedPath = filePath + "." + newExt;
console.log("converting", filePath, "to", convertedPath);
console.log("converting", filePath, "to", convertedPath, "progress_cb:",progress_callback);
var convertArgs = (codec == "mp4") ? [
"-i", filePath,
@ -134,8 +134,8 @@ function convertVideo(fileName, filePath, codec, callback, progressCallback) {
ff.stderr.on('data', function (data) {
console.log('[ffmpeg-video] stderr: ' + data);
if (progressCallback) {
progressCallback(data);
if (progress_callback) {
progress_callback(data);
}
});
@ -263,8 +263,6 @@ var resizeAndUploadImage = function(a, mimeType, size, fileName, fileNameOrg, im
a.h = Math.round(size.height*factor);
a.updated_at = new Date();
db.packArtifact(a);
a.save().then(function() {
fs.unlink(originalFilePath, function (err) {
if (err){
@ -280,7 +278,7 @@ var resizeAndUploadImage = function(a, mimeType, size, fileName, fileNameOrg, im
};
module.exports = {
convert: function(a, fileName, localFilePath, payloadCallback, progressCallback) {
convert: function(a, fileName, localFilePath, payloadCallback, progress_callback) {
getMime(fileName, localFilePath, function(err, mimeType){
console.log("[convert] fn: "+fileName+" local: "+localFilePath+" mimeType:", mimeType);
@ -311,7 +309,7 @@ module.exports = {
var s3Key = "s"+ a.space_id.toString() + "/a" + a._id.toString() + "/" + fileName;
uploader.uploadFile(s3Key, "image/gif", localFilePath, function(err, url) {
if (err) payloadCallback(err);
if(err)callback(err);
else{
console.log(localFilePath);
var stats = fs.statSync(localFilePath);
@ -330,8 +328,6 @@ module.exports = {
a.h = Math.round(size.height*factor);
a.updated_at = new Date();
db.packArtifact(a);
a.save().then(function() {
fs.unlink(localFilePath, function (err) {
if (err){
@ -380,7 +376,7 @@ module.exports = {
else callback(null, url);
});
}
}, progressCallback);
}, progress_callback);
}
},
mp4: function(callback) {
@ -396,7 +392,7 @@ module.exports = {
else callback(null, url);
});
}
}, progressCallback);
}, progress_callback);
}
},
original: function(callback){
@ -438,7 +434,9 @@ module.exports = {
db.packArtifact(a);
a.updated_at = new Date();
a.save().then(function() {
a.save(function(err) {
if (err) payloadCallback(err, null);
else {
fs.unlink(localFilePath, function (err) {
if (err){
console.error(err);
@ -448,6 +446,7 @@ module.exports = {
payloadCallback(null, a);
}
});
}
});
}
});

View File

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

View File

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

View File

@ -44,7 +44,8 @@
"sure": "Are you sure?",
"specify": "Please Specify",
"confirm": "Please Confirm",
"error_unknown_email": "This email/password combination is unknown.",
"signup_google": "Sign In with Google",
"error_unknown_email": "This email/password combination is unknown. Try login with Google.",
"error_password_confirmation": "The entered passwords don't match.",
"error_domain_blocked": "Your domain is blocked.",
"error_user_email_already_used": "This email address is already in use.",

View File

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

View File

@ -16,8 +16,7 @@ module.exports = (req, res, next) => {
else db.User.findOne({where: {_id: session.user_id}})
.then(user => {
if (!user) {
var domain = (process.env.NODE_ENV == "production") ? new URL(config.get('endpoint')).hostname : req.headers.hostname;
res.clearCookie('sdsession', { domain: domain });
res.clearCookie('sdsession');
if (req.accepts("text/html")) {
res.send("Please clear your cookies and try again.");

View File

@ -57,6 +57,11 @@ module.exports = (req, res, next) => {
"_id": spaceId
}}).then(function(space) {
//.populate("creator", userMapping)
//if (err) {
// res.status(400).json(err);
//} else {
if (space) {
if (space.access_mode == "public") {
if (space.password) {

View File

@ -1,4 +1,7 @@
const Umzug = require('umzug');
//'use strict';
//var mongoose = require('mongoose');
//const sqlite3 = require('sqlite3').verbose();
function sequel_log(a,b,c) {
console.log(a);
@ -91,8 +94,7 @@ module.exports = {
user_id: Sequelize.STRING,
role: Sequelize.STRING,
code: Sequelize.STRING,
state: {type: Sequelize.STRING, defaultValue: "pending"}, // valid: "pending", "active"
email_invited: Sequelize.STRING,
state: {type: Sequelize.STRING, defaultValue: "pending"},
created_at: {type: Sequelize.DATE, defaultValue: Sequelize.NOW},
updated_at: {type: Sequelize.DATE, defaultValue: Sequelize.NOW}
}),
@ -197,7 +199,7 @@ module.exports = {
updated_at: {type: Sequelize.DATE, defaultValue: Sequelize.NOW}
}),
init: async function() {
init: function() {
User = this.User;
Session = this.Session;
Space = this.Space;
@ -254,45 +256,26 @@ module.exports = {
as: 'space'
});
await sequelize.sync();
var umzug = new Umzug({
storage: 'sequelize',
storageOptions: {
sequelize: sequelize
},
migrations: {
params: [
sequelize.getQueryInterface(),
Sequelize
],
path: './models/migrations',
pattern: /\.js$/
}
});
umzug.up().then(function(migrations) {
console.log('Migration complete up!');
});
sequelize.sync();
},
getUserRoleInSpace: (originalSpace, user, cb) => {
originalSpace.path = [];
console.log("getUserRoleInSpace",originalSpace._id,user._id,user.home_folder_id);
if (originalSpace._id == user.home_folder_id || (originalSpace.creator_id && originalSpace.creator_id == user._id)) {
cb("admin");
} else {
var findMembershipsForSpace = function(space, allMemberships, prevRole) {
Membership.findAll({ where: {
"space_id": space._id
"space": space._id
}}).then(function(parentMemberships) {
var currentMemberships = parentMemberships.concat(allMemberships);
if (space.parent_space_id) {
Space.findOne({ where: {
"_id": space.parent_space_id
}}).then(function(parentSpace) {
}}, function(err, parentSpace) {
findMembershipsForSpace(parentSpace, currentMemberships, prevRole);
});
} else {
@ -337,26 +320,26 @@ module.exports = {
},
unpackArtifact: (a) => {
if (a.tags && (typeof a.tags)=="string") {
if (a.tags) {
a.tags = JSON.parse(a.tags);
}
if (a.control_points && (typeof a.control_points)=="string") {
if (a.control_points) {
a.control_points = JSON.parse(a.control_points);
}
if (a.payload_alternatives && (typeof a.payload_alternatives)=="string") {
if (a.payload_alternatives) {
a.payload_alternatives = JSON.parse(a.payload_alternatives);
}
return a;
},
packArtifact: (a) => {
if (a.tags && (typeof a.tags)!="string") {
if (a.tags) {
a.tags = JSON.stringify(a.tags);
}
if (a.control_points && (typeof a.control_points)!="string") {
if (a.control_points) {
a.control_points = JSON.stringify(a.control_points);
}
if (a.payload_alternatives && (typeof a.payload_alternatives)!="string") {
if (a.payload_alternatives) {
a.payload_alternatives = JSON.stringify(a.payload_alternatives);
}
return a;

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

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

Before

Width:  |  Height:  |  Size: 5.0 KiB

View File

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

Before

Width:  |  Height:  |  Size: 4.9 KiB

View File

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

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -41,7 +41,7 @@ var SpacedeckBoardArtifacts = {
if ("medium_for_object" in this) {
var medium = this.medium_for_object[a._id];
if (medium && a._id != this.editing_artifact_id) {
medium.value(a.description.toString());
medium.value(a.description);
}
}
},
@ -89,8 +89,7 @@ var SpacedeckBoardArtifacts = {
artifact_is_text_blank: function(a) {
if(a.description){
desc = a.description.toString();
var filtered = desc.replace(/<[^>]+>/g,"").replace(/\s/g,"");
var filtered = a.description.replace(/<[^>]+>/g,"").replace(/\s/g,"");
return (filtered.length<1);
}else{
return false;
@ -587,7 +586,7 @@ var SpacedeckBoardArtifacts = {
var selected = this.selected_artifacts();
if (selected.length<2) return;
var sorted = _.sortBy(selected, function(a) { return a.x+a.y*this.active_space.width }.bind(this));
var sorted = _.sortBy(selected, function(a) { return a.x+a.y*this.active_space.advanced.width }.bind(this));
var minx = sorted[0].x;
var miny = sorted[0].y;
@ -596,13 +595,11 @@ var SpacedeckBoardArtifacts = {
var blocks = [];
var padding = 10;
for (var i=0; i<sorted.length; i++) {
var a = sorted[i];
blocks.push({
w: a.w+padding,
h: a.h+padding,
w: a.w,
h: a.h,
a: a
});
}

View File

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

View File

@ -63,8 +63,8 @@ var SpacedeckSections = {
active_style: {
border_radius: 0,
stroke: 0,
font_family: "Inter",
font_size: 36,
font_family: "Avenir W01",
font_size: 18,
line_height: 1.5,
letter_spacing: 0,
@ -110,30 +110,18 @@ var SpacedeckSections = {
color_picker_opacity: 255,
swatches: [
{id:1, hex:"#ff00ff"},
{id:2, hex:"#ffff00"},
{id:3, hex:"#00ffff"},
{id:5, hex:"#ff0000"},
{id:6, hex:"#00ff00"},
{id:7, hex:"#0000ff"},
{id:8, hex:"#000000"},
{id:9, hex:"#222222"},
{id:10, hex:"#444444"},
{id:11, hex:"#888888"},
{id:12, hex:"#bbbbbb"},
{id:13, hex:"#dddddd"},
{id:14, hex:"#ffffff"},
{id:20, hex:"#4a2f7e"},
{id:21, hex:"#9b59b6"},
{id:22, hex:"#3498db"},
{id:23, hex:"#2ecc71"},
{id:24, hex:"#f1c40f"},
{id:25, hex:"#e67e22"},
{id:26, hex:"#d55c4b"},
{id:27, hex:"#6f4021"},
{id:29, hex:"#95a5a6"},
{id:30, hex:"rgba(0,0,0,0)"},
{id:0, hex:"#4a2f7e"},
{id:1, hex:"#9b59b6"},
{id:2, hex:"#3498db"},
{id:3, hex:"#2ecc71"},
{id:4, hex:"#f1c40f"},
{id:5, hex:"#e67e22"},
{id:6, hex:"#d55c4b"},
{id:7, hex:"#6f4021"},
{id:8, hex:"#ffffff"},
{id:9, hex:"#95a5a6"},
{id:10, hex:"#252525"},
{id:11, hex:"rgba(0,0,0,0)"},
],
swatches_text: [
@ -148,8 +136,18 @@ var SpacedeckSections = {
],
fonts: [
"Inter",
"Courier"
"Arial",
"Courier",
"Georgia",
"Verdana",
"Comic Sans MS",
"Montserrat",
"Lato",
"Roboto",
"Crimson Text",
"EB Garamond",
"Vollkorn",
"Avenir W01"
],
detected_text_formats: {},
@ -182,7 +180,7 @@ var SpacedeckSections = {
toolbar_props_in: false,
toolbar_artifacts_x: "-1000px",
toolbar_artifacts_y: "-1000px",
toolbar_artifacts_in: true
toolbar_artifacts_in: false
},
methods: {
@ -1059,7 +1057,7 @@ var SpacedeckSections = {
this.toolbar_props_x = pp.x+"px";
this.toolbar_props_y = pp.y+"px";
//this.hide_toolbar_artifacts();
this.hide_toolbar_artifacts();
}
this.selection_metrics.x1 = sr.x1;
@ -1127,11 +1125,8 @@ var SpacedeckSections = {
var er = this.enclosing_rect(this.active_space_artifacts);
if (!er) return;
// resize space
this.active_space.width =Math.max((parseInt(er.x2/window.innerWidth)+2)*window.innerWidth, window.innerWidth);
this.active_space.height=Math.max((parseInt(er.y2/window.innerHeight)+2)*window.innerHeight, window.innerHeight);
console.log("bounds: ",this.active_space.width,this.active_space.height);
this.active_space.width =Math.max(er.x2+100, window.innerWidth);
this.active_space.height=Math.max(er.y2+100, window.innerHeight);
if (this._last_bounds_width != this.active_space.width ||
this._last_bounds_height != this.active_space.height) {
@ -1549,7 +1544,7 @@ var SpacedeckSections = {
add_artifact: function (space, item_type, url, evt) {
this.active_tool = "pointer";
this.mouse_state = "idle";
//this.hide_toolbar_artifacts();
this.hide_toolbar_artifacts();
if (!url && (item_type == 'image' || item_type == 'video' || item_type == 'embed')) {
url = prompt("URL?");
@ -1729,7 +1724,7 @@ var SpacedeckSections = {
var a = {
space_id: this.active_space._id,
mime: "x-spacedeck/shape",
description: "",
description: "Text",
x: point.x,
y: point.y,
z: point.z,
@ -1741,7 +1736,7 @@ var SpacedeckSections = {
fill_color: "#000000",
shape: shape_type,
valign: "middle",
align: "center",
align: "center"
};
if (this.guest_nickname) {
@ -1794,6 +1789,8 @@ var SpacedeckSections = {
return false;
}
this.hide_toolbar_artifacts();
// 1. create placeholder artifact
var w=300,h=150;
var fill="transparent";
@ -2296,6 +2293,11 @@ var SpacedeckSections = {
if (!pastedText) return;
if (!pastedText.match(/<[a-zA-Z]+>/g)) {
// crappy heuristic if this is actually HTML
pastedText = pastedText.replace(/\n/g,"<br>");
}
this.insert_embedded_artifact(pastedText);
},
@ -2342,6 +2344,32 @@ var SpacedeckSections = {
this.create_artifact_via_embed_url(text);
return;
}
var new_item = {
mime: "text/html",
description: text.replace("\n", "<br />"),
title: "",
space_id: space._id
};
var w = 400;
var h = 300;
var point = this.find_place_for_item(w,h);
new_item.x = point.x;
new_item.y = point.y;
new_item.w = w;
new_item.h = h;
new_item.z = point.z;
if (this.guest_nickname) {
new_item.editor_name = this.guest_nickname;
}
save_artifact(new_item, function(saved_item) {
this.update_board_artifact_viewmodel(saved_item);
this.active_space_artifacts.push(saved_item);
}.bind(this));
},
create_artifact_via_embed_url: function(url) {
@ -2500,11 +2528,20 @@ var SpacedeckSections = {
this.opened_dialog = "none";
if (files && files.length) {
console.log("file: ",files[0]);
for (var i=0; i<files.length; i++) {
var file = files[i];
if (file.type === "application/pdf") {
var point = {x: 100, y: 100}; //fixme, center upload?
this.dropped_point = point;
this.pending_pdf_file = file;
this.activate_modal('pdfoptions');
} else {
this.create_artifact_via_upload(null, file, true);
}
}
}
},
handle_image_file_upload: function(evt) {
@ -2541,11 +2578,12 @@ var SpacedeckSections = {
},
hide_toolbar_props: function() {
// FIXME test
//this.toolbar_props_in = false;
this.toolbar_props_in = false;
},
show_toolbar_artifacts: function(x,y) {
this.toolbar_artifacts_x = (x-175)+"px";
this.toolbar_artifacts_y = y+"px";
this.toolbar_artifacts_in = true;
},
@ -2555,19 +2593,29 @@ var SpacedeckSections = {
start_adding_artifact: function(evt) {
evt = fixup_touches(evt);
// toggle
if (this.toolbar_artifacts_in) {
this.hide_toolbar_artifacts();
return;
}
this.show_toolbar_artifacts(evt.pageX,evt.pageY);
},
start_drawing_scribble: function(evt) {
this.hide_toolbar_artifacts();
this.active_tool = "scribble";
this.opened_dialog = "none";
},
start_drawing_arrow: function(evt) {
this.hide_toolbar_artifacts();
this.active_tool = "arrow";
this.opened_dialog = "none";
},
start_drawing_line: function(evt) {
this.hide_toolbar_artifacts();
this.active_tool = "line";
this.opened_dialog = "none";
},
@ -2846,6 +2894,32 @@ var SpacedeckSections = {
}.bind(this),500);
},
approve_pdf_upload: function(evt,approve_pdf_upload, mode){
this.close_modal();
if(mode == "classic"){
this.create_artifact_via_upload(evt, this.pending_pdf_file, false);
}
if(mode == "grid") {
this.global_spinner = true;
save_pdf_file(this.active_space, this.dropped_point, this.pending_pdf_file, approve_pdf_upload, function(createdArtifacts){
this.global_spinner = false;
_.each(createdArtifacts, function(new_artifact){
this.update_board_artifact_viewmodel(new_artifact);
this.active_space_artifacts.push(new_artifact)
}.bind(this));
}.bind(this), function(xhr) {
this.global_spinner = false;
alert("Error PDF ("+xhr.status+")");
}.bind(this));
}
},
handle_data_drop: function(evt) {
if (this.active_space_role=="viewer") {
return false;
@ -2858,8 +2932,17 @@ var SpacedeckSections = {
if (files && files.length) {
for (var i=0; i<files.length; i++) {
var file = files[i];
if (file.type === "application/pdf") {
var point = this.cursor_point_to_space(evt);
this.dropped_point = point;
this.pending_pdf_file = file;
this.activate_modal('pdfoptions');
} else {
this.create_artifact_via_upload(evt, file, (files.length>1));
}
}
} else {
var json = evt.dataTransfer.getData('application/json');

View File

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

View File

@ -11,7 +11,6 @@ SpacedeckUsers = {
login_email: "",
login_password: "",
signup_password: "",
signup_invite_code: "",
signup_password_confirmation: "",
account_remove_error: null,
loading_user: false,
@ -31,6 +30,12 @@ SpacedeckUsers = {
if (on_success) {
on_success(user);
}
// see spacedeck_account.js
load_importables(this.user, function(files) {
this.importables = files;
}.bind(this));
}.bind(this), function() {
// error
this.loading_user = false;
@ -43,6 +48,10 @@ SpacedeckUsers = {
},
finalize_login: function(session_token, on_success) {
if(!window.socket_auth || window.socket_auth == '' || window.socket_auth == 'null') {
window.socket_auth = session_token;
}
this.load_user(function(user) {
if (this.invitation_token) {
accept_invitation(this.invitation_token, function(memberships){
@ -117,7 +126,7 @@ SpacedeckUsers = {
signup_guest: function(on_success) {
},
signup_submit: function($event, name, email, password, password_confirmation, invite_code, on_success) {
signup_submit: function($event, name, email, password, password_confirmation, on_success) {
this.creating_user = true;
this.signup_error = null;
@ -131,7 +140,7 @@ SpacedeckUsers = {
$event.stopPropagation();
}
create_user(name, email, password, password_confirmation, invite_code, function(session) {
create_user(name, email, password, password_confirmation, function(session) {
this.creating_user = false;
this.login_submit(email, password, null, on_success);
}.bind(this), function(req) {
@ -147,8 +156,8 @@ SpacedeckUsers = {
}.bind(this));
},
signup_submit_modal: function($event, name, email, password, password_confirmation, invite_code) {
this.signup_submit($event, name, email, password, password_confirmation, invite_code, function() {
signup_submit_modal: function($event, name, email, password, password_confirmation) {
this.signup_submit($event, name, email, password, password_confirmation, function() {
alert("Success.");
location.reload();
});
@ -208,17 +217,15 @@ SpacedeckUsers = {
confirm_password_reset(password, this.reset_token, function(parsed,req) {
if(req.status==201){
alert("New password set successfully.");
this.active_view = "login";
} else {
alert("An unknown error occured.");
}
}.bind(this), function(req) {
if (req.status==404) {
alert("Error: Unknown user.");
var msg = "user not found";
} else {
alert("Error: "+req.statusText);
var msg = "error: " + req.statusText;
}
this.password_reset_confirm_error = msg;
}.bind(this));
},

View File

@ -101,13 +101,11 @@ SpacedeckWebsockets = {
}
if (this.websocket && this.websocket.readyState==1) {
var token = "";
if (this.user) token = this.user.token;
var auth_params = {
action: "auth",
editor_auth: space_auth,
editor_name: this.guest_nickname,
auth_token: token,
auth_token: window.socket_auth,
space_id: space._id
};
console.log("[websocket] auth space");

View File

@ -80,16 +80,10 @@ function setup_whiteboard_directives() {
evt.stopPropagation();
}
if ($scope.active_tool == "zoom") return;
if (evt.which == 2) {
// middle mouse button
this.handle_mouse_down_space(evt);
return;
}
var a = $scope.find_artifact_by_id(evt.currentTarget.id.replace("artifact-",""));
if ($scope.active_tool == "zoom") return;
if ($scope.active_tool == "eyedrop") {
var arts = $scope.selected_artifacts();
if (!$scope.is_selected(a) && arts.length > 0) {
@ -202,9 +196,7 @@ function setup_whiteboard_directives() {
},
handle_mouse_down_space: function(evt) {
if (evt.which != 2) {
if (evt.target != evt.currentTarget && !_.include(["wrapper"],evt.target.className)) return;
}
var $scope = this.vm.$root;
@ -222,7 +214,7 @@ function setup_whiteboard_directives() {
this.deselect();
this.mouse_state = "transform";
$scope.mouse_state = this.mouse_state;
this.start_drawing_note(evt);
this.start_adding_note(evt);
return;
} else if ($scope.active_tool=="arrow") {
@ -500,7 +492,6 @@ function setup_whiteboard_directives() {
if (!xdists[0] || xdists[0][0]>TOL) {
results.snapx = [0,x]; // distance, coordinate
} else {
// FIXME snap rulers are broken
//$scope.snap_ruler_x = xdists[0][1];
}
if (!ydists[0] || ydists[0][0]>TOL) {
@ -525,41 +516,6 @@ function setup_whiteboard_directives() {
return point;
},
start_drawing_note: function(evt) {
evt.preventDefault();
evt.stopPropagation();
var $scope = this.vm.$root;
var point = this.cursor_point_to_space(evt);
this.offset_point_in_wrapper(point);
var z = $scope.highest_z()+1;
var a = {
space_id: $scope.active_space._id,
mime: "text/html",
description: "<p>Text</p>",
x: point.x,
y: point.y,
z: z,
w: 64,
h: 64,
align: "center",
valign: "middle",
stroke_color: "#000000",
fill_color: "rgb(241, 196, 15)",
stroke: 0
};
$scope.save_artifact(a, function(saved_a) {
$scope.update_board_artifact_viewmodel(saved_a);
$scope.active_space_artifacts.push(saved_a);
$scope.select(evt,a);
$scope.transform_ox = 0;
$scope.transform_oy = 0;
$scope.begin_transaction();
}.bind(this));
},
start_drawing_scribble: function(evt) {
evt.preventDefault();
evt.stopPropagation();
@ -735,7 +691,7 @@ function setup_whiteboard_directives() {
this.mouse_state = "idle";
$scope.mouse_state = this.mouse_state;
this.lasso = null;
//$scope.active_tool = "pointer"; disabled to keep the same tool (eg. "Scribble") after drawing a single line.
$scope.active_tool = "pointer";
$scope.end_transaction();
$scope.show_toolbar_props();
@ -895,7 +851,7 @@ function setup_whiteboard_directives() {
var scale_x = lead_x ? (moved_x)/lead_x : 1;
var scale_y = lead_y ? (moved_y)/lead_y : 1;
if ($scope.transform_lock) scale_y = scale_x;
if (!$scope.transform_lock) scale_y = scale_x;
$scope.update_selected_artifacts(function(a) {
var old_a = $scope.find_artifact_before_transaction(a);

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -53,7 +53,14 @@ router.get('/', (req, res) => {
space_id: req.space._id
}}).then(artifacts => {
async.map(artifacts, (a, cb) => {
db.unpackArtifact(a);
//a = a.toObject(); TODO
if (a.control_points) {
a.control_points = JSON.parse(a.control_points);
}
if (a.payload_alternatives) {
a.payload_alternatives = JSON.parse(a.payload_alternatives);
}
if (a.user_id) {
// FIXME JOIN
@ -123,11 +130,10 @@ router.post('/:artifact_id/payload', function(req, res, next) {
var writeStream = fs.createWriteStream(localFilePath);
var stream = req.pipe(writeStream);
var progressCallback = function(progressMsg) {
a.description = progressMsg.toString();
db.packArtifact(a);
var progress_callback = function(progress_msg) {
a.description = progress_msg;
a.save();
redis.sendMessage("update", "Artifact", a, req.channelId);
redis.sendMessage("update", a, a.toJSON(), req.channelId);
};
stream.on('finish', function() {
@ -137,7 +143,7 @@ router.post('/:artifact_id/payload', function(req, res, next) {
db.Space.update({ updated_at: new Date() }, {where: {_id: req.space._id}});
res.distributeUpdate("Artifact", artifact);
}
}, progressCallback);
}, progress_callback);
});
} else {
res.status(401).json({
@ -165,7 +171,6 @@ router.put('/:artifact_id', function(req, res, next) {
}}).then(rows => {
db.unpackArtifact(newAttr);
db.Space.update({ updated_at: new Date() }, {where: {_id: req.space._id} });
newAttr._id = a._id;
res.distributeUpdate("Artifact", newAttr);
});
});

View File

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

View File

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

View File

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

View File

@ -1,6 +1,5 @@
"use strict";
var config = require('config');
const os = require('os');
const db = require('../../models/db');
const Sequelize = require('sequelize');
const Op = Sequelize.Op;
@ -48,11 +47,73 @@ router.get('/', function(req, res, next) {
error: "auth required"
});
} else {
if (req.query.search) {
if (req.query.writablefolders) {
db.Membership.find({where: {
user_id: req.user._id
}}, (memberships) => {
var validMemberships = memberships.filter((m) => {
if (!m.space_id || (m.space_id == "undefined"))
return false;
return true;
});
var editorMemberships = validMemberships.filter((m) => {
return (m.role == "editor") || (m.role == "admin")
});
var spaceIds = editorMemberships.map(function(m) {
return m.space_id;
});
// TODO port
var q = {
"space_type": "folder",
"$or": [{
"creator": req.user._id
}, {
"_id": {
"$in": spaceIds
},
"creator": {
"$ne": req.user._id
}
}]
};
db.Space
.findAll({where: q})
.then(function(spaces) {
var updatedSpaces = spaces.map(function(s) {
var spaceObj = s; //.toObject();
return spaceObj;
});
async.map(spaces, (space, cb) => {
Space.getRecursiveSubspacesForSpace(space, (err, spaces) => {
var allSpaces = spaces;
cb(err, allSpaces);
})
}, (err, spaces) => {
var allSpaces = _.flatten(spaces);
var onlyFolders = _.filter(allSpaces, (s) => {
return s.space_type == "folder";
})
var uniqueFolders = _.unique(onlyFolders, (s) => {
return s._id;
})
res.status(200).json(uniqueFolders);
});
});
});
} else if (req.query.search) {
db.Membership.findAll({where:{
user_id: req.user._id
}}).then(memberships => {
// search for spaces
var validMemberships = memberships.filter(function(m) {
if (!m.space_id || (m.space_id == "undefined"))
@ -81,7 +142,6 @@ router.get('/', function(req, res, next) {
});
} else if (req.query.parent_space_id && req.query.parent_space_id != req.user.home_folder_id) {
// list spaces in a folder
db.Space
.findOne({where: {
@ -115,8 +175,6 @@ router.get('/', function(req, res, next) {
});
} else {
// list home folder and spaces/folders that the user is a member of
db.Membership.findAll({ where: {
user_id: req.user._id
}}).then(memberships => {
@ -125,7 +183,6 @@ router.get('/', function(req, res, next) {
var validMemberships = memberships.filter(function(m) {
if (!m.space_id || (m.space_id == "undefined"))
return false;
return true;
});
var spaceIds = validMemberships.map(function(m) {
@ -170,18 +227,14 @@ router.post('/', function(req, res, next) {
attrs.creator_id = req.user._id;
attrs.edit_hash = crypto.randomBytes(64).toString('hex').substring(0, 7);
attrs.edit_slug = slug(attrs.name);
attrs.access_mode = "private";
db.Space.create(attrs).then(createdSpace => {
res.status(201).json(createdSpace);
// create initial admin membership
//if (err) res.sendStatus(400);
var membership = {
_id: uuidv4(),
user_id: req.user._id,
space_id: attrs._id,
role: "admin",
state: "active"
role: "admin"
};
db.Membership.create(membership).then(() => {
@ -260,17 +313,8 @@ router.put('/:id', function(req, res) {
newAttr.edit_slug = slug(newAttr['name']);
delete newAttr['_id'];
delete newAttr['editor_name'];
delete newAttr['creator'];
delete newAttr['creator_id'];
delete newAttr['space_type'];
if (req['spaceRole'] != "admin") {
delete newAttr['access_mode']
delete newAttr['password']
delete newAttr['edit_hash']
delete newAttr['edit_slug']
delete newAttr['editors_locking']
}
db.Space.update(newAttr, {where: {
"_id": space._id
@ -317,6 +361,43 @@ router.post('/:id/background', function(req, res, next) {
});
});
var handleDuplicateSpaceRequest = function(req, res, parentSpace) {
Space.duplicateSpace(req.space, req.user, 0, (err, newSpace) => {
if (err) {
console.error(err);
res.status(400).json(err);
} else {
res.status(201).json(newSpace);
}
}, parentSpace);
}
router.post('/:id/duplicate', (req, res, next) => {
if (req.query.parent_space_id) {
Space.findOne({
_id: req.query.parent_space_id
}).populate('creator', userMapping).exec((err, parentSpace) => {
if (!parentSpace) {
res.status(404).json({
"error": "parent space not found for duplicate"
});
} else {
db.getUserRoleInSpace(parentSpace, req.user, (role) => {
if (role == "admin" ||  role == "editor") {
handleDuplicateSpaceRequest(req, res, parentSpace);
} else {
res.status(403).json({
"error": "not authed for parent_space_id"
});
}
});
}
});
} else {
handleDuplicateSpaceRequest(req, res);
}
});
router.delete('/:id', function(req, res, next) {
if (req.user) {
const space = req.space;
@ -336,4 +417,142 @@ router.delete('/:id', function(req, res, next) {
}
});
router.post('/:id/artifacts-pdf', function(req, res, next) {
if (req.spaceRole == "editor" || req.spaceRole == "admin") {
var withZones = (req.query.zones) ? req.query.zones == "true" : false;
var fileName = (req.query.filename || "upload.bin").replace(/[^a-zA-Z0-9\.]/g, '');
var localFilePath = "/tmp/" + fileName;
var writeStream = fs.createWriteStream(localFilePath);
var stream = req.pipe(writeStream);
req.on('end', function() {
var rawName = fileName.slice(0, fileName.length - 4);
var outputFolder = "/tmp/" + rawName;
var rights = 777;
fs.mkdir(outputFolder, function(db) {
var images = outputFolder + "/" + rawName + "-page-%03d.jpeg";
// FIXME not portable
exec.execFile("gs", ["-sDEVICE=jpeg", "-dDownScaleFactor=4", "-dDOINTERPOLATE", "-dNOPAUSE", "-dJPEGQ=80", "-dBATCH", "-sOutputFile=" + images, "-r250", "-f", localFilePath], {}, function(error, stdout, stderr) {
if (error === null) {
glob(outputFolder + "/*.jpeg", function(er, files) {
var count = files.length;
var delta = 10;
var limitPerRow = Math.ceil(Math.sqrt(count));
var startX = parseInt(req.query.x, delta);
var startY = parseInt(req.query.y, delta);
async.mapLimit(files, 20, function(localfilePath, cb) {
var fileName = path.basename(localfilePath);
var baseName = path.basename(localfilePath, ".jpeg");
var number = parseInt(baseName.slice(baseName.length - 3, baseName.length), 10);
gm(localFilePath).size(function(err, size) {
var w = 350;
var h = w;
var x = startX + (((number - 1) % limitPerRow) * w);
var y = startY + ((parseInt(((number - 1) / limitPerRow), 10) + 1) * w);
var userId;
if (req.user)
userId = req.user._id;
var a = new Artifact({
mime: "image/jpg",
space_id: req.space._id,
user_id: userId,
editor_name: req.guest_name,
board: {
w: w,
h: h,
x: x,
y: y,
z: (number + (count + 100))
}
});
payloadConverter.convert(a, fileName, localfilePath, (error, artifact) => {
if (error) res.status(400).json(error);
else {
if (withZones) {
var zone = new Artifact({
mime: "x-spacedeck/zone",
description: "Zone " + (number),
space_id: req.space._id,
user_id: userId,
editor_name: req.guest_name,
board: {
w: artifact.board.w + 20,
h: artifact.board.h + 40,
x: x - 10,
y: y - 30,
z: number
},
style: {
order: number,
valign: "middle",
align: "center"
}
});
zone.save((err) => {
redis.sendMessage("create", "Artifact", zone.toJSON(), req.channelId);
cb(null, [artifact, zone]);
});
} else {
cb(null, [artifact]);
}
}
});
});
}, function(err, artifacts) {
// FIXME not portable
exec.execFile("rm", ["-r", outputFolder], function(err) {
res.status(201).json(_.flatten(artifacts));
async.eachLimit(artifacts, 10, (artifact_or_artifacts, cb) => {
if (artifact_or_artifacts instanceof Array) {
_.each(artifact_or_artifacts, (a) => {
redis.sendMessage("create", "Artifact", a.toJSON(), req.channelId);
});
} else  {
redis.sendMessage("create", "Artifact", artifact_or_artifacts.toJSON(), req.channelId);
}
cb(null);
});
});
});
});
} else {
console.error("error:", error);
// FIXME not portable
exec.execFile("rm", ["-r", outputFolder], function(err) {
fs.unlink(localFilePath);
res.status(400).json({});
});
}
});
});
});
} else {
res.status(401).json({
"error": "no access"
});
}
});
module.exports = router;

View File

@ -3,7 +3,6 @@
var config = require('config');
const db = require('../../models/db');
const uuidv4 = require('uuid/v4');
const os = require('os');
var mailer = require('../../helpers/mailer');
var uploader = require('../../helpers/uploader');
@ -26,15 +25,8 @@ var glob = require('glob');
router.get('/current', function(req, res, next) {
if (req.user) {
var u = _.clone(req.user.dataValues);
delete u.password_hash;
delete u.password_reset_token;
delete u.confirmation_token;
u.token = req.cookies['sdsession'];
console.log(u);
res.status(200).json(u);
console.log(req.user.team);
res.status(200).json(req.user);
} else {
res.status(401).json({"error":"user_not_found"});
}
@ -51,18 +43,12 @@ router.post('/', function(req, res) {
var nickname = req.body["nickname"];
var password = req.body["password"];
var password_confirmation = req.body["password_confirmation"];
var invite_code = req.body["invite_code"];
if (password_confirmation != password) {
res.status(400).json({"error":"password_confirmation"});
return;
}
if (config.invite_code && invite_code != config.invite_code) {
res.status(400).json({"error":"Invalid Invite Code."});
return;
}
if (!validator.isEmail(email)) {
res.status(400).json({"error":"email_invalid"});
return;
@ -89,31 +75,28 @@ router.post('/', function(req, res) {
res.sendStatus(400);
})
.then(u => {
var homeFolder = {
var homeSpace = {
_id: uuidv4(),
name: req.i18n.__("home"),
space_type: "folder",
creator_id: u._id
};
db.Space.create(homeFolder)
db.Space.create(homeSpace)
.error(err => {
res.sendStatus(400);
})
.then(homeFolder => {
u.home_folder_id = homeFolder._id;
.then(homeSpace => {
u.home_folder_id = homeSpace._id;
u.save()
.then(() => {
// home folder created,
// auto accept pending invites
db.Membership.update({
"state": "active"
}, {
where: {
"email_invited": u.email,
"state": "pending"
res.status(201).json({});
mailer.sendMail(u.email, req.i18n.__("confirm_subject"), req.i18n.__("confirm_body"), {
action: {
link: config.endpoint + "/confirm/" + u.confirmation_token,
name: req.i18n.__("confirm_action")
}
});
res.status(201).json({});
})
.error(err => {
res.status(400).json(err);
@ -128,6 +111,7 @@ router.post('/', function(req, res) {
db.User.findAll({where: {email: email}})
.then(users => {
if (users.length == 0) {
//var domain = email.slice(email.lastIndexOf('@')+1);
createUser();
} else {
res.status(400).json({"error":"user_email_already_used"});
@ -176,35 +160,36 @@ router.post('/:id/password', function(req, res, next) {
});
});
} else {
res.status(403).json({"error": "Please enter the correct current password."});
res.status(403).json({"error": "old password wrong"});
}
} else {
res.status(403).json({"error": "Access denied."});
res.status(403).json({"error": "wrong user"});
}
} else {
res.status(400).json({"error": "Please choose a new password with at least 6 characters."});
res.status(400).json({"error": "password_to_short"});
}
});
router.delete('/:id', (req, res, next) => {
const user = req.user;
if(user._id == req.params.id) {
if (user.account_type == 'email') {
if (bcrypt.compareSync(req.query.password, user.password_hash)) {
// TODO: this doesn't currently work.
// all objects (indirectly) belonging to the user have
// to be walked and deleted first.
user.destroy().then(err => {
user.remove((err) => {
if(err)res.status(400).json(err);
else res.sendStatus(204);
});
} else {
res.bad_request("Please enter the correct current password.");
res.bad_request("password_incorrect");
}
} else {
res.status(403).json({error: "Access denied."});
user.remove((err) => {
if (err) res.status(400).json(err);
else res.sendStatus(204);
});
}
}
else res.status(403).json({error: ""});
});
router.put('/:user_id/confirm', (req, res) => {
@ -230,8 +215,8 @@ router.post('/:user_id/avatar', (req, res, next) => {
const user = req.user;
const filename = "u"+req.user._id+"_"+(new Date().getTime())+".jpeg"
const localFilePath = os.tmpdir()+"/"+filename;
const localResizedFilePath = os.tmpdir()+"/resized_"+filename;
const localFilePath = "/tmp/"+filename;
const localResizedFilePath = "/tmp/resized_"+filename;
const writeStream = fs.createWriteStream(localFilePath);
const stream = req.pipe(writeStream);
@ -260,6 +245,13 @@ router.post('/:user_id/avatar', (req, res, next) => {
});
});
router.post('/feedback', function(req, res, next) {
var text = req.body.text;
// FIXME
mailer.sendMail("support@example.org", "Support Request by " + req.user.email, text, {reply_to: req.user.email});
res.sendStatus(201);
});
router.post('/password_reset_requests', (req, res, next) => {
const email = req.query.email;
db.User.findOne({where: {"email": email}}).then((user) => {
@ -283,16 +275,21 @@ router.post('/password_reset_requests', (req, res, next) => {
router.post('/password_reset_requests/:confirm_token/confirm', function(req, res, next) {
var password = req.body.password;
db.User
User
.findOne({where: {"password_reset_token": req.params.confirm_token}})
.then((user) => {
if (user) {
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(password, salt, function(err, hash) {
user.password_hash = hash;
user.password_token = null;
user.save().then(function(updatedUser) {
user.save(function(err, updatedUser){
if (err) {
res.sendStatus(400);
} else {
res.sendStatus(201);
}
});
});
});
@ -310,4 +307,19 @@ router.post('/:user_id/confirm', function(req, res, next) {
res.sendStatus(201);
});
router.get('/:user_id/importables', function(req, res, next) {
glob('*.zip', function(err, files) {
res.status(200).json(files);
});
});
router.get('/:user_id/import', function(req, res, next) {
if (req.query.zip) {
res.send("importing");
importer.importZIP(req.user, req.query.zip);
} else {
res.sendStatus(400);
}
});
module.exports = router;

View File

@ -1,6 +1,7 @@
"use strict";
const config = require('config');
require('../models/db');
const redis = require('../helpers/redis');
const express = require('express');
@ -9,11 +10,6 @@ const router = express.Router();
const mailer = require('../helpers/mailer');
const _ = require('underscore');
const db = require('../models/db');
const Sequelize = require('sequelize');
const Op = Sequelize.Op;
const uuidv4 = require('uuid/v4');
router.get('/', (req, res) => {
res.render('index', { title: 'Spaces' });
});
@ -54,6 +50,10 @@ router.get('/password-confirm/:token', (req, res) => {
res.render('spacedeck', { title: 'Signup' });
});
router.get('/team', (req, res) => {
res.render('spacedeck');
});
router.get('/de/*', (req, res) => {
res.redirect("/t/de");
});
@ -78,6 +78,10 @@ router.get('/en', (req, res) => {
res.redirect("/t/end");
});
router.get('/it', (req, res) => {
res.redirect("/t/en");
});
router.get('/account', (req, res) => {
res.render('spacedeck');
});
@ -116,12 +120,17 @@ router.get('/t/:id', (req, res) => {
});
router.get('/s/:token', (req, res) => {
redis.rateLimit(req.real_ip, "token", function(ok) {
if (ok) {
var token = req.params.token;
if (token.split("-").length > 0) {
token = token.split("-")[0];
}
db.Space.findOne({where: {"edit_hash": token}}).then(function (space) {
Space.findOne({"edit_hash": token}).exec(function (err, space) {
if (err) {
res.status(404).render('not_found', { title: 'Page Not Found.' });
} else {
if (space) {
if(req.accepts('text/html')){
res.redirect("/spaces/"+space._id + "?spaceAuth=" + token);
@ -135,11 +144,55 @@ router.get('/s/:token', (req, res) => {
res.status(404).json({});
}
}
}
});
} else {
res.status(429).json({"error": "Too Many Requests"});
}
});
});
router.get('/spaces/:id', (req, res) => {
if (req.headers['user-agent']) {
if (req.headers['user-agent'].match(/facebook/)) {
Space.findOne({"_id": req.params.id }).exec(function (err, space) {
if (err) {
res.status(400).json(err);
} else {
if (space) {
if (space.access_mode == "public") {
Artifact.find({"space_id": req.params.id }).populate("creator").exec(function(err, artifacts) {
space.artifacts = artifacts;
res.render('facebook', { space: space });
});
} else {
res.redirect("/?error=space_not_accessible");
}
} else {
res.render('not_found', { title: 'Spaces' });
}
}
});
} else {
// not facebook, render javascript
res.render('spacedeck', { title: 'Space' });
}
} else res.render('spacedeck', { title: 'Space' });
});
router.get('/qrcode/:id', function(req, res) {
Space.findOne({"_id": req.params.id}).exec(function(err, space) {
if (space) {
const url = config.get("endpoint") + "/s/"+space.edit_hash;
const code = qr.image(url, { type: 'svg' });
res.type('svg');
code.pipe(res);
} else {
res.status(404).json({
"error": "not_found"
});
}
});
});
module.exports = router;

View File

@ -26,9 +26,6 @@ const serveStatic = require('serve-static');
const isProduction = app.get('env') === 'production';
// workaround for libssl_conf.so error triggered by phantomjs
process.env['OPENSSL_CONF'] = '/dev/null';
console.log("Booting Spacedeck Open… (environment: " + app.get('env') + ")");
app.use(logger(isProduction ? 'combined' : 'dev'));
@ -75,7 +72,7 @@ app.use(helmet.frameguard())
app.use(helmet.xssFilter())
app.use(helmet.hsts({
maxAge: 7776000000,
includeSubDomains: true
includeSubdomains: true
}))
app.disable('x-powered-by');
app.use(helmet.noSniff())

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -28,6 +28,7 @@
line-height: 1.5;
width: 100%;
text-align: left;
color: $medium;
font-weight: normal;
cursor: pointer;
border-radius: $radius;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

12
styles/normalize.scss vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -33,6 +33,9 @@
@include translate3d(0, 0, 0);
// @include backface-visibility(hidden);
-webkit-perspective: 1000;
-moz-perspective: 1000;
-ms-perspective: 1000;
perspective: 1000;
.panel-toggles {
@ -96,6 +99,9 @@
display: table-cell;
vertical-align: middle;
// @include backface-visibility(hidden);
-webkit-perspective: 1000;
-moz-perspective: 1000;
-ms-perspective: 1000;
perspective: 1000;
z-index: 1000;

View File

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

View File

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

View File

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

View File

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

View File

@ -1,30 +1,16 @@
<header id="dialog-header" class="header" v-if="(active_view == 'account' && user)" v-cloak>
<div v-cloak class="header-left pull-left">
<a class="btn btn-dark btn-md btn-round btn-icon" href="/spaces">
<span class="icon icon-svg icon-sd6"></span>
</a>
<h5>Edit Account</h5>
</div>
<div id="team" class="dialog in" style="padding:100px;z-index:20000;position:absolute;width:100%;min-height:100%;background-color:#eee" v-if="active_view == 'account' && user" v-cloak>
<div class="header-right pull-right">
<a class="btn btn-dark btn-md btn-round btn-icon" href="/spaces">
<span class="icon icon-cross-0"></span>
</a>
</div>
</header>
<div class="dialog-freestanding dialog in" v-if="active_view == 'account' && user" v-cloak>
<a href="/spaces" class="btn btn-round btn-icon btn-dark btn-md pull-right" style="position:absolute;top:30px;right:30px"><span class="icon icon-cross-0"></span></a>
<div class="dialog-tabs" style="margin:auto">
<div class="dialog-tab" v-bind:class="{open:account=='profile'}" v-on:click="account='profile'"><span>[[__("profile_caption")]]</span></div>
<div class="dialog-tab" v-bind:class="{open:account=='language'}" v-on:click="account='language'"><span>[[__("language_caption")]]</span></div>
<div class="dialog-tab" v-bind:class="{open:account=='notifications'}" v-on:click="account='notifications'"><span>[[__("notifications_caption")]]</span></div>
<div class="dialog-tab" v-bind:class="{open:account=='password'}" v-on:click="account='password'"><span>[[__("password_caption")]]</span></div>
<div class="dialog-tab" v-if="user.account_type=='email'" v-bind:class="{open:account=='password'}" v-on:click="account='password'"><span>[[__("password_caption")]]</span></div>
<div class="dialog-tab" v-bind:class="{open:account=='terminate'}" v-on:click="account='terminate'"><span>[[__("terminate_caption")]]</span></div>
</div>
<div class="dialog-section text-left">
<div class="dialog-section text-left" style="background-color:#f5f5f5;padding-top:40px;padding-bottom:40px">
<div class="collapse" v-bind:class="{in:account=='profile'}">
<div class="labels-inline relative" style="margin-bottom:40px">
<div class="form-group">
@ -78,7 +64,29 @@
v-on:change="user.email_changed=true"
placeholder="mail@example.com">
<button class="btn btn-md btn-dark" v-on:click=" save_user()" style="margin-top:20px">Save</button>
<button class="btn btn-md btn-darken" v-if="user.account_type=='email'" v-on:click=" save_user()" style="margin-top:20px">[[__("ok")]]</button>
</div>
<div class="form-group" v-if="!user.confirmed_at">
<p v-if="!user.confirmed_at && !account_confirmed_sent">[[__("confirmation_sent_long")]]</p>
<span
class="btn btn-xs btn-stroke-darken btn-round"
v-on:click=" confirm_again()"
v-if="!user.confirmed_at && !account_confirmed_sent"
>[[__("send_again")]]</span>
<p v-if="account_confirmed_sent">
<span class="icon icon-check"></span> <span>[[__("confirmation_sent_another")]]</span>
</p>
</div>
<div class="form-group">
<label class="label">Spacedeck.com Data Import</label>
<p v-if="!importables">No .ZIP files found in Spacedeck application folder.</p>
<ul>
<li v-for="f in importables">{{f}} <button v-on:click="start_zip_import(f)">Start Import</button></li>
</ul>
</div>
</div>
</div>
@ -113,7 +121,7 @@
</div>
<div class="collapse" v-bind:class="{in:account=='password'}">
<h4>Change Password</h4>
<h4 class="modal-title">Change Password</h4>
<div class="modal-section labels-inline">
<div class="form-group">
<label class="label">[[__("current_password")]]</label>
@ -132,17 +140,23 @@
</div>
<div class="modal-footer">
<div class="btn-cluster">
<button
class="btn btn-dark btn-md"
class="btn btn-transparent btn-block"
v-on:click="save_user_password(password_change_current, password_change_new, password_change_new_confirmation);" >
[[__("change_password")]]
</button>
</div>
</div>
</div>
<div class="collapse" v-bind:class="{in:account=='terminate'}">
<h4>Terminate Account</h4>
<div class="modal-section labels-inline">
<div class="">
<p>[[__("terminate_warning")]]</p>
<p>[[__("terminate_warning2")]]</p>
</div>
<div class="labels-inline" v-if="user.account_type == 'email'">
<div class="form-group">
<label class="label">[[__("current_password")]]</label>
<input v-model="account_remove_password" class="input input-white no-b" type="password">
@ -153,15 +167,11 @@
<textarea class="input input-white no-b" v-model="account_remove_feedback"></textarea>
<p class="message">[[__("terminate_reason_caption")]]</p>
</div>
</div>
<div class="modal-section labels-inline">
<div class="center alert alert-danger" v-if="account_remove_error">{{account_remove_error}}</div>
</div>
<div class="modal-footer">
<button class="btn btn-stroke-darken btn-md" v-on:click="remove_account(account_remove_password, account_remove_feedback)">Terminate Account</button>
</div>
<button class="btn btn-transparent btn-block" v-on:click="remove_account(account_remove_password, account_remove_feedback)">[[__("terminate_terminate")]]</button>
</div>
</div>
</div>

View File

@ -1,23 +1,15 @@
<header id="folder-header" class="header" v-if="(active_view == 'folders' && active_folder)" v-cloak>
<div v-cloak class="header-left pull-left">
<a class="btn btn-dark btn-md btn-round btn-icon" href="/spaces">
<span class="icon icon-svg icon-sd6"></span>
<a class="btn btn-stroke-darken btn-md btn-round btn-icon" href="/spaces">
<span class="icon icon-home"></span>
</a>
<button v-if="logged_in && (active_space_role == 'editor' || active_space_role == 'admin')" class="btn btn-dark btn-md btn-round" v-on:click="create_space('space')">[[ __('create_space') ]]</button>
<button v-if="logged_in && (active_space_role == 'editor' || active_space_role == 'admin')" class="btn btn-primary btn-md btn-round" v-on:click="create_space('space')">[[ __('create_space') ]]</button>
<button v-if="logged_in && (active_space_role == 'editor' || active_space_role == 'admin')" class="btn btn-stroke-darken btn-md btn-round" v-on:click="create_space('folder')">
<span>[[ __('create_folder') ]]</span>
<span v-bind:class="{'disabled-pro':!is_pro(user)}">[[ __('create_folder') ]]</span>
</button>
</div>
<label class="relative compact-hidden">
<span class="icon icon-sm icon-zoom no-events absolute-top-left" style="margin: 5px;"></span>
<input id="folder-search"
type="search" name="search"
style="padding-left: 40px !important; margin-right: 10px;"
placeholder="[[ __('search') ]]"
class="input input-md input-white input-round no-b w-2"
v-model="folder_spaces_search" v-on:change="search_spaces">
</label>
<div class="header-right pull-right">
<div class="dropdown top light m-r-20 compact-hidden" v-bind:class="{open : active_dropdown=='folder_sorting'}">
<button class="btn btn-sm btn-nude" v-on:click="activate_dropdown('folder_sorting')">
<span>[[ __('sort_by') ]]</span>:
@ -41,15 +33,29 @@
v-on:click="set_folder_sorting('space_type',false)">
<span>[[ __('type') ]]</span>
</li>
</ul>
</div>
</div>
</div>
<div class="header-right pull-right">
<label class="relative compact-hidden">
<span class="icon icon-sm icon-zoom no-events absolute-top-left" style="margin: 5px;"></span>
<input id="folder-search"
type="search" name="search"
style="padding-left: 40px !important; margin-right: 10px;"
placeholder="[[ __('search') ]]"
class="input input-md input-white input-round no-b w-2"
v-model="folder_spaces_search" v-on:change="search_spaces">
</label>
<button class="btn btn-stroke-darken btn-md btn-round" v-if="!user.confirmed_at" v-on:click="confirm_again()">
<span v-if="!account_confirmed_sent">[[ __('email_unconfirmed') ]]</span>
<span v-if="account_confirmed_sent">[[ __('confirmation_sent') ]]</span>
</button>
<div class="dropdown top right light" v-bind:class="{open: active_dropdown=='account'}">
<button
class="profile-avatar btn btn-md btn-icon btn-dark btn-round"
class="profile-avatar btn btn-md btn-icon btn-darken btn-round"
v-bind:style="background_image_style([user.avatar_thumb_uri])"
v-bind:class="{'has-avatar-image':!!user.avatar_thumb_uri}" v-on:click="show_account();">
<span class="icon icon-user" v-if="logged_in && !user.avatar_thumb_uri"></span></button>
@ -70,6 +76,13 @@
</a>
</li>
<li v-on:click="activate_modal('support')">
<span>
<span class="icon icon-sm icon-info"></span>
<span>[[ __('support') ]]</span>
</span>
</li>
<li v-on:click="logout()">
<span>
<span class="icon icon-sm icon-logout"></span>
@ -81,12 +94,12 @@
</div>
<!--div class="btn-group dark round" id="meta-toggle" style="margin-right:10px">
<div class="btn-group dark round" id="meta-toggle" style="margin-right:10px">
<button class="btn btn-md btn-transparent btn-icon btn-icon" v-on:click="toggle_meta()">
<span class="jewel" style="color: white; background-color: red" v-if="meta_unseen>0">{{meta_unseen}}</span>
<span class="icon icon-menu"></span>
</button>
</div-->
</div>
</div>
</header>
@ -98,8 +111,10 @@
<div id="folder-breadcrumb">
<span v-for="item in active_space_path" class="btn btn-sm btn-transparent" v-sd-droppable="handle_folder_drop;item">
<a href="/{{item.space_type}}s/{{item._id}}">{{item.name}}</a>&nbsp; â–¶</span>
<a v-for="item in active_space_path" type="button" class="btn btn-sm btn-transparent" href="/{{item.space_type}}s/{{item._id}}" v-sd-droppable="handle_folder_drop;item">
<span>{{item.name}}</span>
<span class="seperator">/</span>
</a>
<a v-if="(active_space_role != 'admin')" type="button" class="btn btn-sm btn-transparent">
<span>{{active_folder.name}}</span>
@ -113,10 +128,12 @@
<ul class="select-list">
<li><span class="tile-rename" v-on:click="rename_folder(active_folder)">[[__("rename")]]</span></li>
<li v-if="active_space_role == 'admin'"><span class="tile-share" v-on:click="activate_access()">[[__("share")]]</span></li>
<li><a v-on:click=" open_url('[[config.endpoint]]/api/spaces/'+active_folder._id+'/list')" target="_blank">[[__("list")]]</a></li>
</ul>
</div>
</div>
<div v-if="active_folder._id == user.home_folder_id">
<span>[[ __('home') ]]</span>
</div>
@ -129,6 +146,7 @@
</div>
</div>
<div id="folder-empty" v-if="folder_spaces_filter">
<div v-if="active_profile_spaces | empty?">
<p><b>"{{folder_spaces_filter}}"</b> <br/>[[ __('search_no_results') ]]</p>
@ -156,6 +174,7 @@
<div class="dropdown-menu" role="menu">
<ul class="select-list">
<li v-on:click="duplicate_space(item)"><span><span class="icon icon-sm icon-duplicate"></span>[[ __('duplicate') ]]</span></li>
<li v-on:click="rename_space(item)"><span><span class="icon icon-sm icon-tag"></span>[[ __('rename') ]]</span></li>
<li v-on:click="delete_space(item)"><span><span class="icon icon-sm icon-trash"></span>[[ __('delete') ]]</span></li>
</ul>

View File

@ -1,11 +1,17 @@
<header id="landing-header" class="header" v-cloak v-if="(active_view == 'login' || active_view == 'signup' || active_view == 'password-reset' || active_view == 'password-confirm')">
<div class="header-left">
<a class="btn btn-transparent btn-nude" href="/"><img src="/images/sd6-logo-black.svg" width="190"></a>
<a class="btn btn-transparent btn-nude" href="/"><img src="/images/sd5-logo.svg" width="190"></a>
</div>
<div class="header-right pull-right">
<a v-if="active_view != 'login'" class="btn btn-md btn-dark btn-round" href="/login">[[__("login")]]</a>
<a v-if="active_view != 'signup'" class="btn btn-md btn-dark btn-round" href="/signup">[[__("signup")]]</a>
<span class="btn-group dark round">
{% if (locale != "de") %}<a href="/t/de?r={{active_view}}" class="btn btn-transparent btn-md">Deutsch</a>{% endif %}
{% if (locale != "en") %}<a href="/t/en?r={{active_view}}" class="btn btn-transparent btn-md">English</a>{% endif %}
{% if (locale != "fr") %}<a href="/t/fr?r={{active_view}}" class="btn btn-transparent btn-md">Français</a>{% endif %}
</span>
<a class="btn btn-md btn-dark btn-round" href="/login">[[__("login")]]</a>
<a class="btn btn-md btn-blue btn-round" href="/signup">[[__("signup")]]</a>
</div>
</header>
@ -17,7 +23,10 @@
<div id="login" v-bind:class="{active : active_view == 'login'}">
<div class="content">
<form v-on:submit="login_submit(user_forms_email, login_password, $event)">
<h3>Login</h3>
<span class="btn btn-xs btn-darken pull-right" v-on:click="login_google();">[[__("login_google")]]</span>
<h4>[[__("login")]]</h4>
<div class="tight">
<div class="form-group">
@ -28,15 +37,16 @@
</div>
</div>
<button type="submit" class="btn btn-dark btn-block">
<span v-show="!loading_user">Login</span>
<span v-show="loading_user">Logging in…</span>
<button type="submit" class="btn btn-primary btn-block">
<span v-show="!loading_user">[[__("login")]]</span>
<span v-show="loading_user">[[__("logging_in")]]</span>
</button>
<div class="center alert alert-danger" v-if="login_error">{{login_error}}</div>
<div style="margin-top:2em">
<a href="/password-reset">Forgot Password</a>
<div class="pull-right">
<a class="btn btn-xs btn-darken" href="/signup">[[__("signup")]]</a>&nbsp;
<a class="btn btn-xs btn-darken" href="/password-reset">[[__("reset_password")]]</a>
</div>
</form>
</div>
@ -44,12 +54,23 @@
<div id="signup" v-bind:class="{active : active_view == 'signup'}">
<div class="content">
<form v-on:submit="signup_submit($event, user_forms_name, user_forms_email, signup_password, signup_password_confirmation, signup_invite_code)">
<form v-on:submit="signup_submit($event, user_forms_name, user_forms_email, signup_password, signup_password_confirmation)">
<span class="btn btn-xs btn-darken pull-right" v-on:click="login_google();">[[__("login_google")]]</span>
<h4>[[__("signup")]]</h4>
<div class="tight">
<div class="form-group">
<input class="input" type="email" required id="user-email" v-model="user_forms_email" placeholder="[[__("email")]]" autofocus v-focus>
<input class="input" type="text" id="user-name" v-model="user_forms_name" placeholder="[[__("name")]]" v-focus autofocus>
</div>
</div>
<div class="tight">
<div class="form-group">
<input class="input" type="email" required id="user-email" v-model="user_forms_email" placeholder="[[__("email")]]">
</div>
<div class="form-group">
@ -57,33 +78,21 @@
</div>
<div class="form-group">
<input class="input" id="user-password-confirmation" required type="password" v-model="signup_password_confirmation" placeholder="Repeat Password">
<input class="input" id="user-password-confirmation" required type="password" v-model="signup_password_confirmation" placeholder="[[__("password_confirmation")]]">
</div>
</div>
<div class="tight">
<div class="form-group">
<input class="input" type="text" id="user-name" v-model="user_forms_name" placeholder="Pick a username">
</div>
<!--
Disabled textbox so people don't have to input beta code when registering.
Remember to also set invite_code to an empty string ("") in /config/default.conf!
<div class="form-group">
<input class="input" id="invite-code" required type="text" v-model="signup_invite_code" placeholder="Beta Invite Code">
</div>-->
<div style="margin-top: -7px; margin-bottom: 7px;"><small>By signing up you agree to our <a href="/terms" target="_blank">TOS</a> and <a href="/privacy" target="_blank">Privacy Policy.</a></small><br/>
</div>
<!--div style="margin-top: -7px; margin-bottom: 7px;"><small>By signing up you agree to our <a href="/terms" target="_blank">TOS</a> and <a href="/privacy" target="_blank">Privacy Policy.</a></small><br/>
</div-->
<button class="btn btn-dark btn-block">
<button class="btn btn-primary btn-block">
<span v-if="!creating_user">[[__("signup")]]</span>
<span v-if="creating_user">[[__("signing_up")]]</span>
</button>
<div class="center alert alert-danger" style="width:100%;" v-if="signup_error">{{signup_error}}</div>
<a class="btn btn-link btn-block" href="/login" style="margin-top: 20px">[[__("login")]]</a>
</form>
</div>
</div>
@ -98,12 +107,12 @@
</div>
</div>
<div class="text-center alert alert-danger" v-if="password_reset_error">{{password_reset_error}}</div>
<button class="btn btn-dark btn-block" v-on:click="password_reset_submit($event, reset_email)">[[__("reset_password")]]</button>
<button class="btn btn-primary btn-block" v-on:click="password_reset_submit($event, reset_email)">[[__("reset_password")]]</button>
</form>
</div>
<div class="content" v-if="password_reset_send==true">
<h4>Reset Password</h4>
Please check your email inbox.
<h4>[[__("password_confirmation")]]</h4>
[[__("password_check_inbox")]]
</div>
</div>
@ -114,16 +123,16 @@
<div class="tight">
<div class="form-group">
<input class="input" id="user-password" type="password" v-model="signup_password" placeholder="New Password">
<input class="input" id="user-password" type="password" v-model="signup_password" placeholder="[[__("password")]]">
</div>
<div class="form-group">
<input class="input" id="user-password" type="password" v-model="signup_password_confirmation" placeholder="Repeat Password">
<input class="input" id="user-password" type="password" v-model="signup_password_confirmation" placeholder="[[__("password_confirmation")]]">
</div>
</div>
<div class="text-center alert alert-danger" v-if="password_reset_confirm_error">{{password_reset_confirm_error}}</div>
<button class="btn btn-dark btn-block" v-on:click="password_reset_confirm($event, signup_password, signup_password_confirmation)">[[__("save")]]</button>
<button class="btn btn-primary btn-block" v-on:click="password_reset_confirm($event, signup_password, signup_password_confirmation)">[[__("save")]]</button>
</form>
</div>
</div>

View File

@ -3,7 +3,7 @@
<div class="modal-dialog">
<div class="modal-content" style="width:760px">
<div class="modal-header" style="padding-bottom:0">
<h3 class="text-left">[[__("share")]]: {{access_settings_space.name}}</h3>
<h3 class="text-left"><span class="icon icon-share icon-sm"></span> [[__("share")]]: {{access_settings_space.name}}</h3>
<button type="button" class="btn btn-icon btn-light btn-round close" v-on:click=" close_modal()">
<span class="icon icon-cross-1"></span>
</button>
@ -68,7 +68,7 @@
</div>
<div class="form-group">
<button class="btn btn-primary btn-md" v-on:click="invite_member(access_settings_space, invite_email, invite_message, invite_member_role)"> [[__("invite")]] </button>
<button class="btn btn-primary btn-md btn-round" v-on:click="invite_member(access_settings_space, invite_email, invite_message, invite_member_role)"> [[__("invite")]] </button>
</div>
</div>
@ -85,19 +85,14 @@
</tr>
<tr v-for="member in access_settings_memberships" v-bind:class="member.state">
<td>
<span class="editor-avatar btn btn-md btn-dark btn-icon btn-round"
v-if="member.user"
v-bind:style="background_image_style([member.user.avatar_thumb_uri])"
v-bind:class="{'has-avatar-image':!!member.user.avatar_thumb_uri}">
<span class="icon icon-user" v-if="!member.user.avatar_thumb_uri"></span>
</span>
<span class="editor-avatar btn btn-md btn-round btn-icon icon-hourglass" v-if="!member.user"></span>
<span class="editor-avatar btn btn-xs btn-round btn-icon" v-if="member.user">{{member.user.initials}}</span>
<span class="editor-avatar btn btn-xs btn-round btn-icon icon-hourglass" v-if="!member.user"></span>
</td>
<td>
<span class="editor-name" v-if="member.user && member.state == 'active'">{{member.user.nickname}}</span>
<span class="editor-email" v-if="!member.user || member.state == 'pending'">(pending)</span>
<span class="editor-email" v-if="member.user && member.state == 'active'">({{member.user.email}})</span>
<span class="editor-email" v-if="!member.user || member.state == 'pending'">({{member.email_invited}})</span>
<span class="editor-email" v-if="member.state == 'active'">{{member.user.email}}</span>
<span class="editor-email" v-if="member.state == 'pending'">{{member.email_invited}}</span>
<span class="editor-name" v-if="member.state == 'active'">{{member.user.nickname}}</span>
<span class="editor-email" v-if="member.state == 'pending'">(pending)</span>
</td>
<td>
<div class="form-group">
@ -118,7 +113,7 @@
</p>
<div class="form-group" style="padding-top: 40px">
<button class="btn btn-primary btn-md" v-on:click="close_modal();"> [[__("ok")]] </button>
<button class="btn btn-primary btn-md btn-round" v-on:click="close_modal();"> [[__("ok")]] </button>
</div>
</div>

View File

@ -1,3 +1,131 @@
<!-- FIXME modal -->
<div class="modal" v-if="(duplicate_folders.length > 0)" v-cloak>
<div class="modal-wrapper">
<div class="modal-dialog">
<button type="button" class="btn btn-icon btn-light btn-round close" v-on:click="duplicate_folders = []">
<span class="icon icon-cross-1"></span>
</button>
<div class="modal-content">
<div class="modal-body labels-inline">
<div class="modal-section">
<div class="form-group">
<label>
[[__("duplicate_destination")]]
</label>
</div>
<div class="form-group">
<select v-on:change="duplicate_folder_id=$event.target.value">
<option v-for="f in duplicate_folders" value="{{f._id}}">{{f.name}}</option>
</select>
</div>
<div class="form-group">
<button class="btn btn-md btn-round btn-primary" v-on:click="duplicate_folder_confirm(); ">
<span class="icon-label">[[__("ok")]]</span>
</button>
<button class="btn btn-md btn-round btn-darken pull-right" v-on:click="duplicate_folders = [];">
<span class="icon-label">[[__("cancel")]]</span>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div v-cloak class="header-left" v-show="active_space_loaded">
<div class="btn-group dark">
<div class="pull-left">
<a
class="btn btn-stroke-darken btn-md btn-round btn-icon"
title="[[__("home")]]" href="/spaces"
v-if="(logged_in && !embedded && !active_space.parent_space_id && !guest_nickname)">
<span class="icon icon-home"></span>
</a>
<a
class="btn btn-stroke-darken btn-md btn-round btn-icon"
title="[[__("parent_folder")]]"
href="/folders/{{active_space.parent_space_id}}"
v-if="(active_space.parent_space_id && !guest_nickname)">
<span class="icon icon-arrow-left-light"></span>
</a>
<input class="input input-md input-transparent w-auto"
id="space-title"
v-model="active_space.name" name="title" v-on:keydown="save_space_keydown($event)"
v-if="space_editing_title && logged_in" style="padding-right:0" v-focus>
<span class="input input-md input-transparent w-auto"
v-if="!space_editing_title && logged_in"
v-on:click="edit_space_title()">{{active_space.name}}</span>
<span v-if="!logged_in" class="btn btn-dark btn-round btn-md">{{active_space.name}}</span>
<button class="btn btn-md btn-transparent btn-icon" v-if="space_editing_title" v-on:click="save_space_keydown()">
<span class="icon icon-check"></span>
</button>
<div class="dropdown top left light" v-bind:class="{open: active_dropdown=='space'}">
<button class="btn btn-md btn-icon btn-dark" v-on:click="activate_dropdown('space')">
<span class="icon icon-triangle-down"></span></button>
<div class="dropdown-menu" role="menu">
<ul class="select-list">
<li v-on:click="activate_access()" v-if="logged_in">
<span>
<span class="icon icon-sm icon-share"></span>
<span>[[ __('share') ]]</span>
</span>
</li>
<li v-on:click="edit_space_title()" v-if="logged_in">
<span>
<span class="icon icon-sm icon-tag"></span>
<span>[[ __('rename') ]]</span>
</span>
</li>
<li v-on:click="duplicate_space_into_folder()" v-if="logged_in">
<span>
<span class="icon icon-sm icon-duplicate"></span>
<span>[[ __('duplicate') ]]</span>
</span>
</li>
<li v-on:click="download_space()">
<span>
<span class="icon icon-sm icon-download"></span>
<span>[[ __('download_space') ]]</span>
</span>
</li>
<li v-on:click="activate_modal('support')" v-if="logged_in">
<span>
<span class="icon icon-sm icon-info"></span>
<span>[[ __('support') ]]</span>
</span>
</li>
</ul>
</div>
</div>
</div>
</div>
<span class="btn btn-red btn-md" id="offline-indicator" v-bind:class="{offline: was_offline}" v-on:click="show_offline_help()">[[__("offline")]]</span>
</div>
<div v-cloak class="header-right" v-if="active_space_loaded">
<span v-for="active_user in active_space_users" >
@ -13,7 +141,13 @@
</button>
</span>
<div class="btn-group light round" v-if="zones.length">
<div class="btn-group dark" v-if="active_space_role!='viewer'">
<button class="btn btn-md btn-transparent btn-icon" title="Start Presentation (others follow what you see)" v-on:click="toggle_present_mode()" v-bind:class="{open:present_mode}">
<span class="icon icon-presentation"></span>
</button>
</div>
<div class="btn-group dark round" v-if="zones.length">
<button class="btn btn-md btn-transparent btn-icon" v-on:click="go_to_previous_zone()" title="[[__("Previous Zone")]]">
<span class="icon icon-triangle-4-left"></span>
</button>
@ -25,12 +159,16 @@
</button>
</div>
<!--div class="btn-group light" id="meta-toggle" style="margin-right:10px">
<!--button class="btn btn-md btn-dark btn-round btn-icon" v-on:click="download_space()" title="[[__("download_space")]]">
<span class="icon icon-download"></span>
</button-->
<div class="btn-group dark" id="meta-toggle" style="margin-right:10px">
<button class="btn btn-md btn-transparent btn-icon" v-on:click="toggle_meta()" title="[[__("chat")]]">
<span class="jewel" style="color: white; background-color: red" v-if="meta_unseen>0">{{meta_unseen}}</span>
<span class="icon icon-messages"></span>
</button>
</div-->
</div>
</div>
{% include "./tool/toolbar-elements.html" %}
@ -43,8 +181,9 @@
<div class="space-empty" v-cloak v-if="active_view == 'space' && !present_mode && active_space_artifacts.length == 0">
<div class="table-fake">
<div class="cell">
<p>Use the toolbar to add content.<br>
You can also drop images or sound and video files.</p>
<p>Click anywhere to add content.<br>
You can also drop images, sounds and video<br>
or use copy and paste.</p>
</div>
</div>
</div>
@ -67,7 +206,7 @@
<div id="space-clipboard" style="position:fixed;top:0;left:0;z-index:0;opacity:0;background-color:white"><textarea v-model="selected_artifacts_json" cols="2" rows="2" id="clipboard-ta" class="mousetrap"></textarea></div>
<div class="space-bounds" v-bind:style="{width: (active_space.width*bounds_zoom+1000) + 'px', height: (active_space.height*bounds_zoom+1000) + 'px', 'background-color': active_space.background_color}"></div>
<div class="space-bounds" v-bind:style="{width: active_space.width*bounds_zoom + 'px', height: active_space.height*bounds_zoom + 'px', 'background-color': active_space.background_color}"></div>
<div class="wrapper"
v-bind:style="{
@ -105,6 +244,7 @@
<a class="link btn btn-round btn-primary btn-sm" v-if="a.view.link" v-bind:href="a.view.link" target="_blank">{{a.view.link_caption}}</a>
</span>
<div class="btn btn-xs btn-icon btn-round btn-primary edit" v-show="editing_artifact_id!=a._id && is_selected(a)" v-on:touchstart="delayed_edit_artifact($event)"><span class="icon icon-pencil" v-on:click="toggle_selected_artifact_editing(true)" v-on:"touchstart:delayed_edit_artifact($event)"></span></div>
<input v-show="is_selected(a)" type="text" id="ios-focuser-{{a._id}}" class="ios-focuser">
</div>
@ -126,6 +266,7 @@
<a class="link btn btn-round btn-primary btn-sm" v-if="a.view.link" v-bind:href="a.view.link" target="_blank">{{a.view.link_caption}}</a>
</span>
<div class="btn btn-xs btn-icon btn-round btn-primary edit" v-show="editing_artifact_id!=a._id && is_selected(a)" v-on:touchstart="delayed_edit_artifact($event)"><span class="icon icon-pencil" v-on:click="toggle_selected_artifact_editing(true)" v-on:"touchstart:delayed_edit_artifact($event)"></span></div>
<input v-show="is_selected(a)" type="text" id="ios-focuser-{{a._id}}" class="ios-focuser">
</div>
@ -145,6 +286,11 @@
<source v-for="rep in a.payload_alternatives" v-bind:src="rep.payload_uri" v-bind:type="rep.mime" />
</video>
<a class="btn btn-md btn-icon btn-round btn-primary edit"
v-show="a.mime == 'application/pdf'"
v-bind:href="a.payload_uri" target="_blank"
><span class="icon icon-link"></span></a>
<span v-if="a.view.link.length>0" class="link-wrapper">
<a class="link btn btn-round btn-primary btn-sm" v-if="a.view.link" v-bind:href="a.view.link" target="_blank">{{a.view.link_caption}}</a>
</span>
@ -317,7 +463,7 @@
</div>
<div v-if="active_space_loaded" v-cloak>
<!--div id="minimap"
<div id="minimap"
v-bind:style="{width: ''+(active_space.width/minimap_scale)+'px', height: ''+(active_space.height/minimap_scale)+'px', bottom: '66px', right: '20px'}"
v-if="active_space"
v-on:mousedown="handle_minimap_mousedown($event)"
@ -329,16 +475,17 @@
v-on:mouseup="handle_minimap_mouseup($event)">
<div v-for="a in active_space_artifacts" v-bind:style="{left: ''+(a.x/minimap_scale)+ 'px', top: ''+(a.y/minimap_scale) + 'px', width: ''+(a.w/minimap_scale)+ 'px', height: ''+(a.h/minimap_scale) + 'px'}"></div>
<div class="window" v-bind:style="{left: ''+(scroll_left/minimap_scale) + 'px', top: ''+(scroll_top/minimap_scale)+ 'px', width: ''+(window_width/minimap_scale)+ 'px', height: ''+(window_height/minimap_scale) + 'px'}"></div>
</div-->
<div class="btn-group light zoom-bar">
<button class="btn btn-icon btn-md btn-white" v-on:click="zoom_in()">
</div>
<div class="btn-group dark" style="position:absolute;bottom:20px;right:20px;">
<button class="btn btn-icon btn-md btn-transparent" v-on:click="zoom_in()">
<span class="icon icon-plus"></span>
</button>
<button class="btn btn-md btn-white no-p" v-on:click="zoom_to_original()">
<button class="btn btn-md btn-transparent no-p" v-on:click="zoom_to_original()">
{{viewport_zoom_percent}}%
</button>
<button class="btn btn-icon btn-md btn-white" v-on:click="zoom_out()">
<button class="btn btn-icon btn-md btn-transparent" v-on:click="zoom_out()">
<span class="icon icon-minus"></span>
</button>
</div>

View File

@ -126,12 +126,20 @@
<label class="label label-sm text-center">[[__("font_size")]]</label>
<input class="input no-b no-p text-center text-large" spellcheck="false" type="text" pattern="[0-9]" maxlength="64" v-model="active_style.font_size">
<button tabindex="-1" class="input-drag btn btn-transparent btn-icon" style="cursor: ns-resize;" v-sd-fader="true" sd-fader-var-y="active_style.font_size" sd-fader-min-y="30" sd-fader-max-y="200" sd-fader-sens="5">
<!--button tabindex="-1" class="input-drag btn btn-transparent btn-icon" style="cursor: ns-resize;" v-sd-fader="true" sd-fader-var-y="active_style.font_size" sd-fader-min-y="8" sd-fader-max-y="400" sd-fader-sens="0.2">
<span class="icon icon-triangles-vertical"></span>
</button>
</button-->
<span class="input-unit">px</span>
</div>
<div class="form-group no-m">
<span class="font-size-swatches" v-show="opened_dialog=='color-text'">
<button class="btn btn-sm" v-on:click="apply_font_size(64)" style="font-size:32px">Big</button>
<button class="btn btn-sm" v-on:click="apply_font_size(32)" style="font-size:24px">Medium</button>
<button class="btn btn-sm" v-on:click="apply_font_size(18)" style="font-size:14px">Small</button>
</span>
</div>
<!--div class="form-group no-m">
<label class="label label-sm text-center">[[__("line_height")]]</label>
<input disabled class="input no-b no-p text-center text-large" spellcheck="false" type="text" pattern="[0-9\.]" maxlength="64" v-model="active_style.line_height">

View File

@ -1,5 +1,5 @@
<div id="layout" class="relative">
<div class="dialog-section">
<div class="dialog-section no-p-b">
<div class="btn-group">
<button class="btn btn-transparent btn-icon" v-on:click="layout_stack_top()">
<span class="icon icon-stack-3d-top"></span>

View File

@ -1,8 +1,7 @@
<h4 class="dialog-title">[[__("tool_shape")]]</h4>
<div id="shapes">
<div class="dialog-section">
<div class="btn-group">
<div class="dialog-section no-p-h" style="white-space: normal;">
<button class="btn btn-icon-labeled btn-transparent" v-on:click="add_shape('ellipse',$event)">
<span class="icon icon-shape-circle"></span>
<span class="icon-label">[[__("tool_circle")]]</span>
@ -18,6 +17,11 @@
<span class="icon-label">[[__("tool_square")]]</span>
</button>
<!--button class="btn btn-icon-labeled btn-transparent rot45" v-on:click="add_shape('diamond',$event)">
<span class="icon icon-shape-square"></span>
<span class="icon-label">[[__("tool_diamond")]]</span>
</button-->
<button class="btn btn-icon-labeled btn-transparent" v-on:click="add_shape('speechbubble',$event)">
<span class="icon icon-shape-bubble"></span>
<span class="icon-label">[[__("tool_bubble")]]</span>
@ -43,6 +47,23 @@
<span class="icon-label">[[__("tool_heart")]]</span>
</button>
<button class="btn btn-icon-labeled btn-transparent" v-on:click="start_drawing_arrow()">
<span class="icon icon-tool-arrow"></span>
<span class="icon-label">[[__("tool_arrow")]]</span>
</button>
<button class="btn btn-icon-labeled btn-transparent" v-on:click="start_drawing_line()">
<span class="icon icon-tool-line"></span>
<span class="icon-label">[[__("tool_line")]]</span>
</button>
</div>
</div>
<!--
<div class="dialog-section no-p">
<div class="btn-cluster">
<button class="btn btn-transparent text-center"> Upload </button>
<button class="btn btn-transparent text-center" v-on:click="start_drawing_scribble()"> Draw </button>
</div>
</div>
-->

View File

@ -24,6 +24,6 @@
<!--button class="btn btn-transparent btn-icon-labeled" v-on:click="apply_formatting($event,'insertUnorderedList')">
<span class="icon icon-text-list-bullet"></span>
<span class="icon-label">Bullets</span>
</button--!>
</button-->
</div>
</div>

View File

@ -1,24 +1,21 @@
<div class="toolbar toolbar-elements" v-bind:class="{in:toolbar_artifacts_in,out:!toolbar_artifacts_in}" v-show="!is_active_space_role('viewer') && active_space_loaded">
<div class="toolbar toolbar-elements" v-bind:class="{in:toolbar_artifacts_in,out:!toolbar_artifacts_in}" v-show="!is_active_space_role('viewer') && active_space_loaded" v-bind:style="{left:toolbar_artifacts_x,top:toolbar_artifacts_y}">
<div class="btn-group light vertical">
<div class="btn-group dark">
<a class="btn btn-icon btn-transparent"
title="[[__("home")]]" href="/spaces"
v-if="(!active_space.parent_space_id && !guest_nickname)">
<span class="icon icon-folder"></span>
</a>
<!--div id="search-dialog" class="dropdown bottom light center static" v-bind:class="{open:opened_dialog=='search'}">
<div class="btn-collapse in">
<button class="btn btn-transparent btn-icon-labeled" v-bind:class="{open:opened_dialog=='search'}" v-on:click="open_dialog('search')" >
<span class="icon icon-search"></span>
<span class="icon-label">[[__("tool_search")]]</span>
</button>
</div>
<a class="btn btn-icon btn-dark"
title="Parent Folder"
href="/folders/{{active_space.parent_space_id}}"
v-if="(active_space.parent_space_id && !guest_nickname)">
<div class="dialog dialog-search">
xinclude "./search.html"
</div>
</div-->
<span class="icon icon-sd6 icon-svg"></span>
</a>
<button class="btn btn-divider"></button>
<div class="dropdown top left light" v-bind:class="{open:opened_dialog=='shapes'}">
<div class="dropdown bottom light center" v-bind:class="{open:opened_dialog=='shapes'}">
<div class="btn-collapse in">
<button class="btn btn-transparent btn-icon-labeled" v-bind:class="{open:opened_dialog=='shapes'}" v-on:click="open_dialog('shapes')">
<span class="icon icon-shapes"></span>
@ -31,35 +28,30 @@
</div>
</div>
<button class="btn btn-icon-labeled btn-transparent" v-on:click="start_drawing_scribble()" v-bind:class="{active:active_tool=='scribble'}">
<button class="btn btn-icon-labeled btn-transparent" v-on:click="start_drawing_scribble()">
<span class="icon icon-tool-scribble"></span>
<span class="icon-label">[[__("tool_scribble")]]</span>
</button>
<button class="btn btn-icon-labeled btn-transparent" v-on:click="start_drawing_arrow()" v-bind:class="{active:active_tool=='arrow'}">
<span class="icon icon-tool-arrow"></span>
<span class="icon-label">[[__("tool_arrow")]]</span>
</button>
<div class="dropdown bottom light center">
<div class="btn-collapse in">
<button class="btn btn-transparent btn-icon-labeled" v-on:click="handle_insert_image_url()" v-on:touchstart="handle_insert_image_url()">
<span class="icon icon-upload"></span>
<span class="icon-label" >Media</span>
<span class="icon-label" >[[__("tool_upload")]]</span>
</button>
</div>
</div>
<div class="dropdown bottom light center">
<div class="btn-collapse in">
<button class="btn btn-transparent btn-icon-labeled" v-on:click="active_tool='note'" v-bind:class="{active:active_tool=='note'}">
<button class="btn btn-transparent btn-icon-labeled" v-on:click=" add_artifact(active_space, 'text', null, $event)">
<span class="icon icon-tool-text"></span>
<span class="icon-label">[[__("tool_text")]]</span>
</button>
</div>
</div>
<div class="dropdown top left light">
<div class="dropdown bottom light center">
<div class="btn-collapse">
<button class="btn btn-transparent btn-icon-labeled" v-bind:class="{open:opened_dialog=='image'}" v-on:click="open_dialog('image')">
<span class="icon icon-picture"></span>
@ -72,7 +64,7 @@
</div>
</div>
<div class="dropdown top left light" v-bind:class="{open:opened_dialog=='zones'}">
<div class="dropdown bottom light center" v-bind:class="{open:opened_dialog=='zones'}">
<div class="btn-collapse in">
<button class="btn btn-transparent btn-icon-labeled" v-bind:class="{open:opened_dialog=='zones'}" v-on:click="open_dialog('zones')">
<span class="icon icon-zone"></span>
@ -87,7 +79,7 @@
<button class="btn btn-divider" v-show="logged_in"></button>
<div class="dropdown top left center" v-show="logged_in" v-bind:class="{open:opened_dialog=='background'}">
<div class="dropdown bottom light center" v-show="logged_in" v-bind:class="{open:opened_dialog=='background'}">
<div class="btn-collapse in">
<button class="btn btn-transparent btn-icon-labeled" v-bind:class="{open:opened_dialog=='background'}" v-on:click="open_dialog('background')">
<span class="letter">bg</span>
@ -100,25 +92,6 @@
</div>
</div>
<button class="btn btn-transparent btn-icon-labeled"
v-if="active_space_role=='admin'"
v-on:click="activate_access()">
<span class="icon icon-share"></span>
<span class="icon-label">[[ __('share') ]]</span>
</button>
<!--
<li v-on:click="edit_space_title()" v-if="logged_in">
<span>
<span class="icon icon-sm icon-tag"></span>
<span>[[ __('rename') ]]</span>
</span>
</li>
-->
<button class="btn btn-transparent btn-icon-labeled" title="Start Presentation (others follow what you see)" v-on:click="toggle_present_mode()" v-bind:class="{open:present_mode}">
<span class="icon icon-presentation"></span>
<span class="icon-label">[[ __('present') ]]</span>
</button>
</div>
</div>

View File

@ -1,38 +1,42 @@
<div class="toolbar toolbar-properties" v-cloak v-show="active_space_loaded && !is_active_space_role('viewer')" v-bind:class="{in:toolbar_props_in,out:!toolbar_props_in}" v-if="active_space_loaded">
<div class="toolbar toolbar-properties" v-cloak v-show="active_space_loaded && !is_active_space_role('viewer')" v-bind:class="{in:toolbar_props_in,out:!toolbar_props_in}" v-bind:style="{left:toolbar_props_x,top:toolbar_props_y}" v-if="active_space_loaded">
<div class="btn-group light vertical">
<div class="dropdown top right light"
<div class="btn-group dark">
<div class="dropdown topleft light"
v-bind:class="{open : opened_dialog.match('color') ,
'option-1':opened_dialog=='color-fill' ,
'option-2':opened_dialog=='color-stroke' ,
'option-3':opened_dialog=='color-text',
'options-3':selection_metrics.contains_text}">
<button
class="dropdown-toggle btn btn-icon btn-transparent"
<label
class="dropdown-toggle btn btn-icon btn-transparent no-r-r"
v-on:click="open_dialog('color-fill')"
v-bind:class="{open:opened_dialog=='color-fill'}">
<span class="icon icon-tool-fill icon-sm"></span>
<span class="jewel" v-bind:style="{'background-color':active_style.fill_color}"></span>
</button><br>
<button
class="dropdown-toggle btn btn-icon btn-transparent"
</label>
<label
class="dropdown-toggle btn btn-icon btn-transparent no-r"
v-bind:class="{open:opened_dialog=='color-stroke'}"
v-on:click="open_dialog('color-stroke')">
<span class="icon icon-tool-stroke icon-sm"></span>
<span class="jewel jewel-stroke" v-bind:style="{'border-color':active_style.stroke_color}"></span>
</button><br>
<button
class="dropdown-toggle btn btn-icon btn-transparent"
</label>
<label
class="dropdown-toggle btn btn-icon btn-transparent no-r-l"
v-on:click="open_dialog('color-text')"
v-bind:class="{open:opened_dialog=='color-text'}">
<span class="icon icon-tool-text icon-sm"></span>
<span class="jewel" v-bind:style="{'border-color':active_style.text_color}">{{active_style.font_size}}</span>
</button>
</label>
<div class="dialog">
{% include "./color.html" %}
</div>
</div>
</div>
<div class="btn-group dark">
<!-- <button class="btn btn-transparent btn-icon-labeled">
<span class="icon icon-tool-eyedrop"></span>
<span class="icon-label">Eyedrop</span>
@ -41,11 +45,11 @@
<button class="btn btn-divider"></button>
-->
<div class="dropdown top light right" v-bind:class="{open:opened_dialog=='text-styles'}">
<div class="dropdown top light center" v-bind:class="{open:opened_dialog=='text-styles'}">
<div class="btn-collapse in">
<button class="btn btn-transparent btn-icon-labeled" v-on:click="open_dialog('text-styles')" v-bind:class="{open : opened_dialog=='text-styles'}">
<span class="icon icon-text-styles"></span>
<span class="icon-label">Styles</span>
<span class="icon-label">styles</span>
</button>
</div>
@ -54,7 +58,21 @@
</div>
</div>
<div class="dropdown top light right" v-bind:class="{open:opened_dialog=='type-align'}">
<div class="dropdown top light center" v-bind:class="{open:opened_dialog=='filter'}">
<div class="btn-collapse" v-bind:class="{in:selection_metrics.contains_images}">
<!-- <div class="btn-collapse" v-bind:class="in:selection_metrics.count>0"> -->
<button class="btn btn-transparent btn-icon-labeled" v-on:click="open_dialog('filter')" v-bind:class="{open : opened_dialog=='filter'}">
<span class="icon icon-contrast"></span>
<span class="icon-label">[[__("tool_filter")]]</span>
</button>
</div>
<div class="dialog">
{% include "./filter.html" %}
</div>
</div>
<div class="dropdown top light center" v-bind:class="{open:opened_dialog=='type-align'}">
<div class="btn-collapse" v-bind:class="{in:selection_metrics.contains_text}">
<button class="btn btn-transparent btn-icon-labeled" v-on:click="open_dialog('type-align')" v-bind:class="{open : opened_dialog=='type-align'}">
<span class="icon icon-text-align-left-alt"></span>
@ -67,9 +85,9 @@
</div>
</div>
<div class="dropdown top light right" v-bind:class="{open:opened_dialog=='layout'}">
<div class="dropdown top light center" v-bind:class="{open:opened_dialog=='layout'}">
<div class="btn-collapse in">
<div class="btn-collapse" v-bind:class="{in:selection_metrics.count>0}">
<button class="btn btn-transparent btn-icon-labeled" v-on:click="open_dialog('layout')" v-bind:class="{open : opened_dialog=='layout'}">
<span class="icon icon-cluster"></span>
<span class="icon-label">[[__("tool_layout")]]</span>
@ -81,7 +99,7 @@
</div>
</div>
<div class="dropdown top light right" v-bind:class="{open:opened_dialog=='text-settings'}">
<div class="dropdown top light center" v-bind:class="{open:opened_dialog=='text-settings'}">
<div class="btn-collapse in">
<button class="btn btn-transparent btn-icon-labeled" v-on:click="open_dialog('text-settings')" v-bind:class="{open : opened_dialog=='text-settings'}">
@ -97,7 +115,7 @@
<button class="btn btn-divider"></button>
<div class="dropdown top light right" v-bind:class="{open:opened_dialog=='object-options'}">
<div class="dropdown top light center" v-bind:class="{open:opened_dialog=='object-options'}">
<button class="btn btn-transparent btn-icon-labeled" v-on:click="open_dialog('object-options')" v-bind:class="{open : opened_dialog=='object-options'}">
<span class="icon icon-cogwheel"></span>
<span class="icon-label">[[__("more")]]</span>

View File

@ -2,11 +2,11 @@
<div id="zones" style="max-height:500px;overflow-y:scroll">
<div class="dialog-section">
<!--p v-if="zones.length<2">
<p v-if="zones.length<2">
Turn your Space into a zooming presentation by placing some Zones and switch through them when presenting.
</p-->
</p>
<button v-on:click="add_zone()" class="btn btn-sm btn-dark">[[__("add_zone")]]</button>
<button v-on:click="add_zone()" class="btn btn-sm btn-primary">[[__("add_zone")]]</button>
</div>
<div class="dialog-section no-p" v-for="z in zones | orderBy 'order'" style="white-space: nowrap;text-align:left;cursor:pointer" v-on:click="zoom_to_zone(z)">

View File

@ -9,64 +9,91 @@
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<link href="[[ '/images/favicon.png' | cdn ]]" rel="icon" type="image/x-icon" />
<link href='https://fonts.googleapis.com/css?family=Inter' rel='stylesheet' type='text/css'>
<link href='https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,700,600,800,300|Montserrat:400,700|EB+Garamond|Vollkorn|Lato|Roboto|Source+Code+Pro|Ubuntu|Raleway|Playfair+Display|Crimson+Text' rel='stylesheet' type='text/css'>
<link type="text/css" rel="stylesheet" href="https://fast.fonts.net/cssapi/ee1a3484-4d98-4f9f-9f55-020a7b37f3c5.css"/>
<link rel="stylesheet" href="[[ '/stylesheets/style.css' | cdn ]]">
<script>if (typeof module === 'object') {window.module = module; module = undefined;}</script>
<script src="//cdnjs.cloudflare.com/ajax/libs/twemoji/1.3.2/twemoji.min.js"></script>
<script>
window.socket_auth = '[[socket_auth]]';
window.browser_lang = '[[locale]]';
window.csrf_token = '[[csrf_token]]';
{% if process.env.NODE_ENV != "production" %}
var ENV = {
name: 'development',
webHost: "localhost:9666",
webEndpoint:"http://localhost:9666",
apiEndpoint: "http://localhost:9666",
websocketsEndpoint: "ws://localhost:9666"
};
{% else %}
var ENV = {
name: 'production',
webHost: location.host,
webEndpoint: location.origin,
apiEndpoint: location.origin,
websocketsEndpoint: location.origin.replace("https:","wss:").replace("http:","ws:")
};
{% endif %}
{% if subdomain_team %}
var subdomainTeam = [[ subdomain_team | json | safe ]];
{% else %}
var subdomainTeam = null;
{% endif %}
</script>
<script src="/javascripts/jquery-2.1.4.min.js"></script>
<script src="/javascripts/i18next-1.11.2.js"></script>
<script src="/javascripts/clipboard.js"></script>
{% if process.env.NODE_ENV == "production" %}
<script src="[[ '/javascripts/spacedeck.js' | cdn ]]"></script>
{% else %}
<script minify src="/javascripts/jquery-2.1.4.min.js"></script>
<script minify src="/javascripts/i18next-1.11.2.js"></script>
<script minify src="/javascripts/clipboard.js"></script>
<script src="/javascripts/lodash.compat.js"></script>
<script src="/javascripts/fastclick.js"></script>
<script src="/javascripts/vue.js"></script>
<script src="/javascripts/moment.js"></script>
<script src="/javascripts/medium.patched.js"></script>
<script src="/javascripts/route-recognizer.js"></script>
<script minify src="/javascripts/lodash.compat.js"></script>
<script minify src="/javascripts/fastclick.js"></script>
<script minify src="/javascripts/vue.js"></script>
<script minify src="/javascripts/moment.js"></script>
<script minify src="/javascripts/medium.patched.js"></script>
<script minify src="/javascripts/route-recognizer.js"></script>
<script src="/javascripts/backend.js"></script>
<script src="/javascripts/link_parser.js"></script>
<script src="/javascripts/vector-render.js"></script>
<script src="/javascripts/mousetrap.js"></script>
<script src="/javascripts/smoke.js"></script>
<script src="/javascripts/helper.js"></script>
<script src="/javascripts/packer.growing.js"></script>
<script minify src="/javascripts/backend.js"></script>
<script minify src="/javascripts/link_parser.js"></script>
<script minify src="/javascripts/vector-render.js"></script>
<script minify src="/javascripts/mousetrap.js"></script>
<script minify src="/javascripts/smoke.js"></script>
<script minify src="/javascripts/helper.js"></script>
<script minify src="/javascripts/packer.growing.js"></script>
<script src="/javascripts/spacedeck_routes.js"></script>
<script src="/javascripts/spacedeck_formatting.js"></script>
<script src="/javascripts/spacedeck_sections.js"></script>
<script src="/javascripts/spacedeck_spaces.js"></script>
<script src="/javascripts/spacedeck_teams.js"></script>
<script src="/javascripts/spacedeck_board_artifacts.js"></script>
<script src="/javascripts/spacedeck_users.js"></script>
<script src="/javascripts/spacedeck_account.js"></script>
<script src="/javascripts/spacedeck_modals.js"></script>
<script src="/javascripts/spacedeck_avatars.js"></script>
<script src="/javascripts/spacedeck_websockets.js"></script>
<script minify src="/javascripts/spacedeck_routes.js"></script>
<script minify src="/javascripts/spacedeck_formatting.js"></script>
<script minify src="/javascripts/spacedeck_sections.js"></script>
<script minify src="/javascripts/spacedeck_spaces.js"></script>
<script minify src="/javascripts/spacedeck_teams.js"></script>
<script minify src="/javascripts/spacedeck_board_artifacts.js"></script>
<script minify src="/javascripts/spacedeck_users.js"></script>
<script minify src="/javascripts/spacedeck_account.js"></script>
<script minify src="/javascripts/spacedeck_modals.js"></script>
<script minify src="/javascripts/spacedeck_avatars.js"></script>
<script minify src="/javascripts/spacedeck_websockets.js"></script>
<script src="/javascripts/spacedeck_whiteboard.js"></script>
<script src="/javascripts/spacedeck_directives.js"></script>
<script src="/javascripts/spacedeck_vue.js"></script>
<script minify src="/javascripts/spacedeck_whiteboard.js"></script>
<script minify src="/javascripts/spacedeck_directives.js"></script>
<script minify src="/javascripts/spacedeck_vue.js"></script>
{% endif %}
<script>if (window.module) module = window.module;</script>
</head>
<body id="main" v-bind:class="{'present-mode':present_mode,'modal-open':active_modal}" v-on:click="handle_body_click($event)">
<!--[if lt IE 10]>
<p class="browsehappy">You are using an <strong>outdated</strong> version of Internet Explorer. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
<![endif]-->
{% include "./partials/login.html" %}
{% include "./partials/space.html" %}
{% include "./partials/folders.html" %}
@ -77,6 +104,10 @@
{% include "./partials/modal/access.html" %}
{% include "./partials/modal/folder-settings.html" %}
{% include "./partials/modal/support.html" %}
{% include "./partials/modal/login.html" %}
{% include "./partials/modal/pdfoptions.html" %}
</body>
<script type="text/javascript">