├── styles ├── team.scss ├── canvas.scss ├── form-file.scss ├── form-select.scss ├── filter.scss ├── settings.scss ├── layout.scss ├── updates.scss ├── space-grid.scss ├── section.scss ├── lasso.scss ├── user.scss ├── author.scss ├── close.scss ├── landing.scss ├── video.scss ├── vars.scss ├── overflow.scss ├── row.scss ├── smoke.scss ├── pattern.scss ├── metrics.scss ├── form-range.scss ├── space-profile.scss ├── rich-text.scss ├── login.scss ├── colors.scss ├── ruler.scss ├── pages.scss ├── header.scss ├── icon.scss ├── files.scss ├── guides.scss ├── margin-columns.scss ├── space-guides.scss ├── annotation.scss ├── members.scss ├── alerts.scss ├── column.scss ├── search.scss ├── table.scss ├── editors.scss ├── type.scss ├── chat.scss ├── style.scss ├── main.scss ├── player.scss ├── select-list.scss ├── profile.scss ├── form-checkbox.scss ├── form-input-group.scss └── actions.scss ├── public ├── images │ ├── hue.png │ ├── favicon.png │ ├── huevalue.png │ ├── sd5-logo.png │ ├── spinner.gif │ ├── spinner2.gif │ ├── hourglass.gif │ ├── border-dotted.png │ ├── opacity-grid.png │ ├── opacity-strip.png │ ├── cursors │ │ ├── eyedrop.png │ │ ├── pencil.png │ │ ├── pencil.psd │ │ ├── crosshair.png │ │ ├── crosshair.psd │ │ ├── eyedropper.png │ │ ├── eyedropper.psd │ │ ├── text-frame.png │ │ └── text-frame.psd │ ├── placeholder-4x3.gif │ ├── sd6-screenshot.png │ ├── sd5-hero2-compressed.jpg │ ├── sd5-keyvisual-compressed.jpg │ ├── triangle.svg │ └── diamond.svg ├── fonts │ ├── OpenSans-Regular.ttf │ ├── icon-regular-webfont.eot │ ├── icon-regular-webfont.ttf │ ├── icon-regular-webfont.woff │ └── font.scss └── javascripts │ ├── spacedeck_formatting.js │ ├── spacedeck_updates.js │ ├── spacedeck_modals.js │ ├── spacedeck_avatars.js │ └── spacedeck_account.js ├── views ├── error.html ├── public │ ├── terms.html │ ├── privacy.html │ └── contact.html ├── emails │ └── action.html ├── not_found.html ├── space_list.html ├── partials │ ├── tool │ │ ├── text-digits.html │ │ ├── pick-mobile.html │ │ ├── embed.html │ │ ├── link.html │ │ ├── image.html │ │ ├── audio.html │ │ ├── video.html │ │ ├── object.html │ │ ├── crop.html │ │ ├── zones.html │ │ ├── share.html │ │ ├── text-formats.html │ │ ├── selection.html │ │ ├── text-columns.html │ │ ├── grid.html │ │ ├── text-styles.html │ │ ├── text-align.html │ │ ├── canvas.html │ │ ├── object-options.html │ │ ├── shapes.html │ │ ├── toolbar-object-options.html │ │ ├── filter.html │ │ ├── layout.html │ │ ├── stroke.html │ │ ├── pattern.html │ │ ├── toolbar-text.html │ │ └── search.html │ ├── modal │ │ └── folder-settings.html │ ├── meta-folder.html │ ├── meta.html │ └── team.html ├── artifact_list.html ├── facebook.html ├── index.html ├── layouts │ └── outer.html └── spacedeck.html ├── README.md ├── .gitignore ├── .dockerignore ├── middlewares ├── 500.js ├── error_helpers.js ├── i18n.js ├── 404.js ├── artifact_helpers.js ├── session.js ├── cors.js ├── api_helpers.js └── space_helpers.js ├── Gulpfile.js ├── models ├── action.js └── migrations │ └── 01-spaces-delete-cascade.js ├── config └── default.json ├── routes ├── api │ ├── memberships.js │ ├── webgrabber.js │ ├── sessions.js │ └── space_messages.js └── root.js ├── package.json └── helpers ├── mailer.js ├── phantom.js ├── uploader.js └── redis.js /styles/team.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | @import "mixins"; 3 | 4 | -------------------------------------------------------------------------------- /public/images/hue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mntmn/spacedeck-open/HEAD/public/images/hue.png -------------------------------------------------------------------------------- /public/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mntmn/spacedeck-open/HEAD/public/images/favicon.png -------------------------------------------------------------------------------- /public/images/huevalue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mntmn/spacedeck-open/HEAD/public/images/huevalue.png -------------------------------------------------------------------------------- /public/images/sd5-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mntmn/spacedeck-open/HEAD/public/images/sd5-logo.png -------------------------------------------------------------------------------- /public/images/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mntmn/spacedeck-open/HEAD/public/images/spinner.gif -------------------------------------------------------------------------------- /public/images/spinner2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mntmn/spacedeck-open/HEAD/public/images/spinner2.gif -------------------------------------------------------------------------------- /views/error.html: -------------------------------------------------------------------------------- 1 |

[[ message ]]

2 |

[[ error.status ]]

3 |
[[ error.stack ]]
4 | -------------------------------------------------------------------------------- /public/images/hourglass.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mntmn/spacedeck-open/HEAD/public/images/hourglass.gif -------------------------------------------------------------------------------- /public/images/border-dotted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mntmn/spacedeck-open/HEAD/public/images/border-dotted.png -------------------------------------------------------------------------------- /public/images/opacity-grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mntmn/spacedeck-open/HEAD/public/images/opacity-grid.png -------------------------------------------------------------------------------- /public/images/opacity-strip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mntmn/spacedeck-open/HEAD/public/images/opacity-strip.png -------------------------------------------------------------------------------- /public/fonts/OpenSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mntmn/spacedeck-open/HEAD/public/fonts/OpenSans-Regular.ttf -------------------------------------------------------------------------------- /public/images/cursors/eyedrop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mntmn/spacedeck-open/HEAD/public/images/cursors/eyedrop.png -------------------------------------------------------------------------------- /public/images/cursors/pencil.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mntmn/spacedeck-open/HEAD/public/images/cursors/pencil.png -------------------------------------------------------------------------------- /public/images/cursors/pencil.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mntmn/spacedeck-open/HEAD/public/images/cursors/pencil.psd -------------------------------------------------------------------------------- /public/images/placeholder-4x3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mntmn/spacedeck-open/HEAD/public/images/placeholder-4x3.gif -------------------------------------------------------------------------------- /public/images/sd6-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mntmn/spacedeck-open/HEAD/public/images/sd6-screenshot.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Repository Moved 2 | 3 | Hi, Spacedeck is here: https://github.com/spacedeck/spacedeck-open 4 | 5 | Cheers! 6 | -------------------------------------------------------------------------------- /public/images/cursors/crosshair.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mntmn/spacedeck-open/HEAD/public/images/cursors/crosshair.png -------------------------------------------------------------------------------- /public/images/cursors/crosshair.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mntmn/spacedeck-open/HEAD/public/images/cursors/crosshair.psd -------------------------------------------------------------------------------- /public/images/cursors/eyedropper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mntmn/spacedeck-open/HEAD/public/images/cursors/eyedropper.png -------------------------------------------------------------------------------- /public/images/cursors/eyedropper.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mntmn/spacedeck-open/HEAD/public/images/cursors/eyedropper.psd -------------------------------------------------------------------------------- /public/images/cursors/text-frame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mntmn/spacedeck-open/HEAD/public/images/cursors/text-frame.png -------------------------------------------------------------------------------- /public/images/cursors/text-frame.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mntmn/spacedeck-open/HEAD/public/images/cursors/text-frame.psd -------------------------------------------------------------------------------- /public/fonts/icon-regular-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mntmn/spacedeck-open/HEAD/public/fonts/icon-regular-webfont.eot -------------------------------------------------------------------------------- /public/fonts/icon-regular-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mntmn/spacedeck-open/HEAD/public/fonts/icon-regular-webfont.ttf -------------------------------------------------------------------------------- /public/fonts/icon-regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mntmn/spacedeck-open/HEAD/public/fonts/icon-regular-webfont.woff -------------------------------------------------------------------------------- /public/images/sd5-hero2-compressed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mntmn/spacedeck-open/HEAD/public/images/sd5-hero2-compressed.jpg -------------------------------------------------------------------------------- /public/images/sd5-keyvisual-compressed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mntmn/spacedeck-open/HEAD/public/images/sd5-keyvisual-compressed.jpg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | javascripts/maps 3 | javascripts/spacedeck.js 4 | public/stylesheets/*.css 5 | database.sqlite 6 | *.swp 7 | *~ 8 | 9 | -------------------------------------------------------------------------------- /styles/canvas.scss: -------------------------------------------------------------------------------- 1 | #canvas { 2 | .select-list {background-color: transparent; } 3 | } 4 | #canvas-orientation { 5 | position: absolute; 6 | right: 0; 7 | top: 0; 8 | } 9 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .git 3 | logs 4 | *.log 5 | scripts 6 | pids 7 | *.pid 8 | *.seed 9 | lib-cov 10 | coverage 11 | .grunt 12 | .lock-wscript 13 | build/Release 14 | node_modules -------------------------------------------------------------------------------- /views/public/terms.html: -------------------------------------------------------------------------------- 1 | {% extends '../layouts/outer.html' %} 2 | 3 | {% block title %}[[ __("terms") ]]{% endblock %} 4 | 5 | {% block content %} 6 |
7 |
8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /views/public/privacy.html: -------------------------------------------------------------------------------- 1 | {% extends '../layouts/outer.html' %} 2 | {% block title %}[[ __("privacy") ]]{% endblock %} 3 | 4 | {% block content %} 5 | 6 |
7 | 8 |
9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /views/public/contact.html: -------------------------------------------------------------------------------- 1 | {% extends '../layouts/outer.html' %} 2 | 3 | {% block title %}[[ __("contact") ]]{% endblock %} 4 | 5 | {% block content %} 6 |
7 | 8 |
9 | 10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /middlewares/500.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = (err, req, res, next) => { 4 | console.error(err); 5 | res.status(err.status || 500); 6 | res.render('error', { 7 | message: err.message, 8 | error: {} 9 | }); 10 | } -------------------------------------------------------------------------------- /styles/form-file.scss: -------------------------------------------------------------------------------- 1 | 2 | .file { 3 | input { 4 | visibility: hidden; 5 | width: 0; 6 | height: 0; 7 | position: absolute; 8 | } 9 | display: inline-block !important; 10 | overflow: hidden; 11 | cursor: pointer; 12 | } -------------------------------------------------------------------------------- /views/emails/action.html: -------------------------------------------------------------------------------- 1 | [[ text | safe ]] 2 | 3 | {% if options.message %} 4 |

5 | [[options.message]] 6 |

7 | {% endif %} 8 | {% if options.action %}

9 | [[options.action.name]]
10 | {% endif %} 11 | 12 | -------------------------------------------------------------------------------- /views/not_found.html: -------------------------------------------------------------------------------- 1 | {% extends 'layouts/outer.html' %} 2 | 3 | {% block title %}[[ __("not_found") ]]{% endblock %} 4 | 5 | {% block content %} 6 | 7 |
8 |

[[__("not_found")]]

9 |
10 | 11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /styles/form-select.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | @import "mixins"; 3 | 4 | .input-select { 5 | background-color: rgba(255,255,255,0.04); 6 | background-image: url('images/select_arrow.gif'); 7 | border-radius: $radius; 8 | display: inline-block; 9 | width: 100%; 10 | } 11 | 12 | select { 13 | appearance:none; 14 | padding-left: 0px; 15 | width: 100%; 16 | } 17 | -------------------------------------------------------------------------------- /Gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp') 2 | const sass = require('gulp-sass') 3 | const concat = require('gulp-concat') 4 | 5 | gulp.task('styles', function(done) { 6 | gulp.src('styles/**/*.scss') 7 | .pipe(sass({ 8 | errLogToConsole: true 9 | })) 10 | .pipe(gulp.dest('./public/stylesheets/')) 11 | .pipe(concat('style.css')) 12 | done() 13 | }) 14 | -------------------------------------------------------------------------------- /middlewares/error_helpers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = (req, res, next) => { 4 | res.bad_request = (msg) => { 5 | if (req.accepts('text/html')) { 6 | res.status(400).render('error', { 7 | message: msg 8 | }); 9 | } else { 10 | res.status(400).json({ 11 | "error": msg 12 | }); 13 | } 14 | } 15 | next(); 16 | } 17 | -------------------------------------------------------------------------------- /styles/filter.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | 3 | #filter { 4 | 5 | .icon { 6 | position: absolute; 7 | opacity: 0.5; 8 | color: $medium; 9 | left: 0; 10 | top: -4px; 11 | display: none; 12 | } 13 | 14 | .form-group { 15 | .btn-less { bottom: 0; left: 0px; } 16 | .btn-more { bottom: 0; right: 0px;} 17 | 18 | .label { 19 | margin-bottom: -5px; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /middlewares/i18n.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('../models/db'); 4 | var config = require('config'); 5 | 6 | module.exports = (req, res, next) => { 7 | req.i18n.setLocale(req.i18n.prefLocale); 8 | 9 | if (req.cookies.spacedeck_locale) { 10 | req.i18n.setLocaleFromCookie(); 11 | } 12 | 13 | if (req.user && req.user.prefs_language) { 14 | req.i18n.setLocale(req.user.prefs_language); 15 | } 16 | next(); 17 | } 18 | -------------------------------------------------------------------------------- /public/fonts/font.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'icon'; 3 | src: url('../fonts/icon-regular-webfont.eot'); 4 | src: url('../fonts/icon-regular-webfont.eot?#iefix') format('embedded-opentype'), 5 | url('../fonts/icon-regular-webfont.woff') format('woff'), 6 | url('../fonts/icon-regular-webfont.ttf') format('truetype'), 7 | url('../fonts/icon-regular-webfont.svg#iconregular') format('svg'); 8 | font-weight: normal; 9 | font-style: normal; 10 | } 11 | -------------------------------------------------------------------------------- /styles/settings.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | @import "mixins"; 3 | 4 | #settings { 5 | position: absolute; 6 | top: 0px; 7 | left: 0px; 8 | width: 100%; 9 | height: 100%; 10 | z-index: 1000; 11 | .overflow-y-scroll { 12 | width: 100%; 13 | padding: 25px 40px; 14 | } 15 | 16 | font-family: 'open sans', sans-serif; 17 | background-color: $light; 18 | color: $medium;; 19 | h5 { 20 | font-size: 13px; 21 | line-height: 30px; 22 | display: block; 23 | } 24 | } -------------------------------------------------------------------------------- /views/space_list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

[[ __("folder") ]]: [[space.name]]

5 | 6 | 7 | 8 | 9 | 10 | 11 | {% for s in subspaces %} 12 | 13 | 14 | 15 | 16 | 17 | {% endfor %} 18 |
[[__("created")]][[__("name")]][[__("link")]]
[[ s.created_at | date('d.m.Y H:i') ]][[ s.name ]][[ s.ae_link ]]
19 | 20 | 21 | -------------------------------------------------------------------------------- /views/partials/tool/text-digits.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 12 |
13 |
14 | -------------------------------------------------------------------------------- /middlewares/404.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('../models/db'); 4 | var config = require('config'); 5 | 6 | module.exports = (req, res, next) => { 7 | var err = new Error('Not Found'); 8 | if (req.accepts('text/html')) { 9 | res.status(404).render('not_found', { 10 | title: 'Page Not Found.' 11 | }); 12 | } else if (req.accepts('application/json')) { 13 | res.status(404).json({ 14 | "error": "not_found" 15 | }); 16 | } else { 17 | res.status(404).send("Not Found."); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /styles/layout.scss: -------------------------------------------------------------------------------- 1 | #layout { 2 | #align { 3 | .btn-group { 4 | height: 120px; 5 | width: 180px; 6 | position: relative; 7 | .btn { 8 | position: absolute; 9 | display: inline-block; 10 | 11 | &.top { top: 0px; left: 50%; margin-left: -30px; } 12 | &.bottom { bottom: 0px; left: 50%; margin-left: -30px; } 13 | &.left { left: 0px; top: 50%; margin-top: -30px; } 14 | &.right { right: 0px; top: 50%; margin-top: -30px; } 15 | } 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /middlewares/artifact_helpers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const db = require('../models/db'); 3 | const Sequelize = require('sequelize'); 4 | const Op = Sequelize.Op; 5 | 6 | var config = require('config'); 7 | 8 | module.exports = (req, res, next) => { 9 | var artifactId = req.params.artifact_id; 10 | db.Artifact.findOne({where: { 11 | "_id": artifactId 12 | }}).then(artifact => { 13 | if (artifact) { 14 | req['artifact'] = artifact; 15 | next(); 16 | } else { 17 | res.sendStatus(404); 18 | } 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /views/partials/tool/pick-mobile.html: -------------------------------------------------------------------------------- 1 |
2 |

Mobile Upload

3 | 4 | 5 | 6 |

7 | Install the Spacedeck App on your phone and scan this QR code to upload photos, sound, video or text. 8 |

9 |

10 | Access Code: {{active_space.edit_hash}} 11 |

12 |
13 | -------------------------------------------------------------------------------- /views/partials/tool/embed.html: -------------------------------------------------------------------------------- 1 |

Embed Space

2 | 3 |
4 |
5 |

6 | Copy and paste this HTML code into your blog or website: 7 |

8 |
9 |
10 |
11 |
12 | 13 |
14 |
15 |
-------------------------------------------------------------------------------- /public/images/triangle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /styles/updates.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | @import "mixins"; 3 | 4 | #updates { 5 | list-style: none; 6 | padding: 0; 7 | margin: 0; 8 | > li { 9 | padding: 0 0; 10 | border-bottom: 1px solid rgba(0,0,0,0.05); 11 | .avatar { 12 | border-radius: 100%; 13 | vertical-align: middle; 14 | margin-right: 10px; 15 | } 16 | > span, 17 | > a { 18 | vertical-align: middle; 19 | line-height: 60px; 20 | } 21 | a {color: $blue; } 22 | .date { 23 | float: right; 24 | display: inline-block; 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /public/javascripts/spacedeck_formatting.js: -------------------------------------------------------------------------------- 1 | /* 2 | SpacedeckFormatting 3 | This module contains functions dealing with Rich Text Formatting. 4 | */ 5 | 6 | var SpacedeckFormatting = { 7 | apply_formatting: function(section, cmd, arg1, arg2) { 8 | console.log("apply_formatting: ",section,cmd); 9 | 10 | var scribe = _scribe_handle_for_object[section._id]; 11 | var command = scribe.getCommand(cmd); 12 | 13 | if (cmd == 'createLink') { 14 | arg1 = prompt("Link URL?"); 15 | } 16 | 17 | scribe.el.focus(); 18 | command.execute(arg1,arg2); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /views/partials/tool/link.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 7 |
8 | 9 | 12 |
13 |
14 | -------------------------------------------------------------------------------- /styles/space-grid.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | @import "mixins"; 3 | 4 | $grid-color-1: rgba(60,60,60,0.125); 5 | $grid-color-2: rgba(60,60,60,0.075); 6 | 7 | .edge { 8 | width: 100%; 9 | height: 100%; 10 | border:1px dotted $light; 11 | pointer-events:none; 12 | opacity: 0.5; 13 | position:absolute; 14 | z-index: 100000; 15 | display: none; 16 | } 17 | 18 | .space-grid { 19 | pointer-events:none; 20 | position:absolute; 21 | width:100%; 22 | height:100%; 23 | top:0; 24 | left:0; 25 | opacity: 0.5; 26 | } 27 | 28 | .grid-active #space-helpers { 29 | display: block !important; 30 | } 31 | -------------------------------------------------------------------------------- /styles/section.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | @import "mixins"; 3 | 4 | .section { 5 | // min-height: 100%; 6 | min-height: 50px !important; 7 | position: relative; 8 | //@include clearfix; 9 | background-color: #fff; 10 | background-position: center; 11 | background-size: cover; 12 | 13 | &.active { 14 | .section-padding-corner.in, 15 | .section-padding.in { 16 | display: block; 17 | } 18 | } 19 | } 20 | 21 | .space-empty, 22 | .section-empty { 23 | position: absolute; 24 | left: 0; 25 | top: 0; 26 | bottom: 0; 27 | right: 0; 28 | z-index: 800; 29 | pointer-events: none; 30 | display: block; 31 | } 32 | -------------------------------------------------------------------------------- /styles/lasso.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | @import "mixins"; 3 | 4 | #lasso { 5 | border-radius: $radius; 6 | @include backface-visibility(hidden); 7 | 8 | background-color: rgba(40,140,215,0.125); 9 | position:absolute; 10 | z-index: 2500; 11 | pointer-events: none; 12 | opacity: 0; 13 | border: 1px solid rgba(255,255,255,0.5); 14 | 15 | &:after{ 16 | border-radius: $radius; 17 | border: 1px dotted rgba(40,140,215,1); 18 | content: ""; 19 | display: block; 20 | position: absolute; 21 | height: auto; 22 | width: auto; 23 | top: -1px; 24 | left: -1px; 25 | right: -1px; 26 | bottom: -1px; 27 | } 28 | } -------------------------------------------------------------------------------- /models/action.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // FIXME port this last model 4 | 5 | var mongoose = require('mongoose'); 6 | var Schema = mongoose.Schema; 7 | 8 | module.exports.actionSchema = mongoose.Schema({ 9 | space: { 10 | type: Schema.Types.ObjectId, 11 | ref: 'Space' 12 | }, 13 | user: { 14 | type: Schema.Types.ObjectId, 15 | ref: 'User' 16 | }, 17 | editor_name: String, 18 | action: String, 19 | object: Schema.Types.Mixed, 20 | created_at: { 21 | type: Date, 22 | default: Date.now 23 | }, 24 | updated_at: { 25 | type: Date, 26 | default: Date.now 27 | } 28 | }); 29 | 30 | module.exports.actionSchema.index({ 31 | space: 1, 32 | created_at: 1 33 | }); 34 | 35 | -------------------------------------------------------------------------------- /views/artifact_list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

[[space.name]]

4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | {% for a in space.artifacts %} 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | {% endfor %} 22 |
createdupdatedfiletypefilenamepreview
[[ a.created_at | date('d.m.Y H:i') ]] by [[ a.user.email ]][[ a.editor_name ]][[ a.updated_at | date('d.m.Y H:i') ]] by [[ a.update_user.email ]][[ a.last_update_editor_name ]][[ a.mime ]]{% if a.payload_uri %}[[ a.filename ]]{% endif %}[[ a.description ]]
23 | 24 | 25 | -------------------------------------------------------------------------------- /styles/user.scss: -------------------------------------------------------------------------------- 1 | // .member { 2 | // position: relative; 3 | // padding: 15px 25px; 4 | // border-top: 1px solid rgba(255,255,255,0.05) !important; 5 | 6 | // .member-avatar { 7 | // background-color: $blue; 8 | // font-family: $main-font; 9 | // color: white; 10 | // display: inline-block; 11 | // height: 30px; 12 | // width: 30px; 13 | // line-height: 30px; 14 | // text-align: center; 15 | // border-radius: $radius; 16 | // background-size: cover; 17 | // background-position: center; 18 | // margin-right: 10px; 19 | // float: left; 20 | // } 21 | 22 | // .member-email, 23 | // .member-name { 24 | // font-weight: 300; 25 | // opacity: 0.5; 26 | // overflow: hidden; 27 | // text-overflow: ellipsis; 28 | // white-space: nowrap; 29 | // } 30 | // } 31 | -------------------------------------------------------------------------------- /views/facebook.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | [[space.name]] 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

[[space.name]]

16 | {% for a in space.artifacts %} 17 | 18 | [[ a.mime ]] 19 | [[ a.description | striptags ]] 20 | {% if a.payload_uri %}download{% endif %} 21 | 22 | {% endfor %} 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /views/partials/tool/image.html: -------------------------------------------------------------------------------- 1 |

Image

2 | 3 |
4 |
5 | 9 |

Click to Upload
or drag file(s) anywhere.

10 |
11 |
12 | 13 |
14 | 15 |
16 | -------------------------------------------------------------------------------- /public/javascripts/spacedeck_updates.js: -------------------------------------------------------------------------------- 1 | /* 2 | SpacedeckUpdates 3 | This module contains functions dealing with Updates / Notifications. 4 | */ 5 | 6 | SpacedeckUpdates = { 7 | 8 | user_notifications: [], 9 | updates_items: [], 10 | 11 | update_name_for_key: function(key) { 12 | 13 | var updates_mapping = { 14 | 'space_like': "liked", 15 | 'space_comment': "commented in", 16 | 'space_follow': "is now following", 17 | 'space_publish': "published a new version of" 18 | } 19 | 20 | var name = updates_mapping[key]; 21 | if(name) return name; 22 | return key; 23 | }, 24 | 25 | load_updates: function() { 26 | 27 | load_notifications(this.user, function(notifications) { 28 | this.user_notifications = notifications; 29 | }); 30 | }, 31 | 32 | activate_updates: function() { 33 | $location.path("/updates"); 34 | } 35 | } -------------------------------------------------------------------------------- /views/partials/tool/audio.html: -------------------------------------------------------------------------------- 1 |

audio

2 | 3 |
4 | 5 |
6 | 10 |

Click to Upload
or drag file(s) here

11 |
12 |
13 | 14 |
15 | 16 |
17 | -------------------------------------------------------------------------------- /views/partials/tool/video.html: -------------------------------------------------------------------------------- 1 |

Video

2 | 3 |
4 |
5 | 9 |

Click to Upload
or drag file(s) anywhere.

10 |
11 |
12 | 13 |
14 | 15 |
16 | -------------------------------------------------------------------------------- /public/images/diamond.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /styles/author.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | 3 | .author { 4 | position: relative; 5 | padding-top: 7px; 6 | padding-right: 15px; 7 | padding-left: 60px; 8 | margin-left: -60px; 9 | margin-right: -25px; 10 | border-top: 1px solid rgba(255,255,255,0.05) !important; 11 | 12 | .author-avatar { 13 | background-color: $blue; 14 | font-family: $main-font; 15 | color: white; 16 | display: inline-block; 17 | height: 30px; 18 | width: 30px; 19 | line-height: 30px; 20 | text-align: center; 21 | border-radius: $radius; 22 | background-size: cover; 23 | background-position: center; 24 | } 25 | 26 | .author-time, 27 | .author-name { 28 | font-weight: 300; 29 | font-size: 10px; 30 | line-height: 10px; 31 | opacity: 0.5; 32 | font-family: $main-font; 33 | } 34 | .author-time { 35 | position: absolute; 36 | right: 15px; 37 | top: 15px; 38 | } 39 | } 40 | 41 | #chat .author-avatar{ 42 | position: absolute; 43 | top: 14px; 44 | left: 15px; 45 | } -------------------------------------------------------------------------------- /styles/close.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | @import "mixins"; 3 | 4 | // 5 | // Close icons 6 | // -------------------------------------------------- 7 | 8 | 9 | .close { 10 | position: absolute; 11 | top: 0px; 12 | right: 0px; 13 | height: 60px; 14 | width: 60px; 15 | text-align: center; 16 | line-height: 60px; 17 | font-size: 40px; 18 | color: $dark !important; 19 | opacity: 0.5; 20 | outline: 0 !important; 21 | 22 | &:focus { 23 | @include tab-focus(); 24 | } 25 | 26 | &:hover, 27 | &:focus { 28 | text-decoration: none; 29 | cursor: pointer; 30 | } 31 | &:active { 32 | @include opacity(1); 33 | } 34 | 35 | // [converter] extracted button& to button.close 36 | } 37 | 38 | // Additional properties for button version 39 | // iOS requires the button element instead of an anchor tag. 40 | // If you want the anchor version, it requires `href="#"`. 41 | button.close { 42 | padding: 0; 43 | cursor: pointer; 44 | background: transparent; 45 | border: 0; 46 | -webkit-appearance: none; 47 | } 48 | -------------------------------------------------------------------------------- /styles/landing.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | 3 | #landing-header { 4 | background-color: white; 5 | height: 64px; 6 | position: relative; 7 | top: 0; 8 | left: 0; 9 | right: 0; 10 | } 11 | 12 | #landing { 13 | margin-top: 100px; 14 | 15 | section { 16 | margin-left: 300px; 17 | 18 | > * { 19 | max-width: 600px; 20 | } 21 | } 22 | } 23 | 24 | .footer { 25 | margin-left: 300px; 26 | margin-top: 100px; 27 | margin-bottom: 100px; 28 | } 29 | 30 | @media screen and (max-width: 1000px) { 31 | #landing { 32 | section { 33 | margin-left: 20px; 34 | margin-right: 20px; 35 | } 36 | } 37 | .footer { 38 | margin-left: 20px; 39 | margin-right: 20px; 40 | } 41 | 42 | .header-right { 43 | right: auto; 44 | padding-left: 10px; 45 | padding-right: 20px; 46 | padding-top: 80px; 47 | } 48 | 49 | #folder-wrapper { 50 | padding-top: 128px; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /views/partials/tool/object.html: -------------------------------------------------------------------------------- 1 |
2 |

Selection

3 | 4 |
5 | 9 |
10 | 11 |
12 | 16 |
17 | 18 |
19 | 23 |
24 |
25 | -------------------------------------------------------------------------------- /views/partials/tool/crop.html: -------------------------------------------------------------------------------- 1 |

Crop

(Not yet implemented)

2 | 3 |
4 |
5 |
6 | 7 |
8 | 50 9 | 10 |
11 |
12 |
13 | 14 |
15 |
16 | 17 |
18 | 21 | 24 | 27 |
28 |
29 |
30 |
-------------------------------------------------------------------------------- /styles/video.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | @import "mixins"; 3 | 4 | video { 5 | -webkit-appearance:none; 6 | } 7 | 8 | video::-webkit-media-controls-panel {} 9 | 10 | video::-webkit-media-controls-play-button {} 11 | 12 | video::-webkit-media-controls-volume-slider-container {} 13 | 14 | video::-webkit-media-controls-volume-slider {} 15 | 16 | video::-webkit-media-controls-mute-button {} 17 | 18 | video::-webkit-media-controls-timeline {} 19 | 20 | video::-webkit-media-controls-current-time-display {} 21 | 22 | video::-webkit-full-page-media::-webkit-media-controls-panel {} 23 | 24 | video::-webkit-media-controls-timeline-container {} 25 | 26 | video::-webkit-media-controls-time-remaining-display {} 27 | 28 | video::-webkit-media-controls-seek-back-button {} 29 | 30 | video::-webkit-media-controls-seek-forward-button {} 31 | 32 | video::-webkit-media-controls-fullscreen-button {} 33 | 34 | video::-webkit-media-controls-rewind-button {} 35 | 36 | video::-webkit-media-controls-return-to-realtime-button {} 37 | 38 | video::-webkit-media-controls-toggle-closed-captions-button {} 39 | -------------------------------------------------------------------------------- /styles/vars.scss: -------------------------------------------------------------------------------- 1 | $radius: 3px !default; 2 | $unit: 60px !default; 3 | 4 | $blue: #3d9ee9; 5 | $blue-light: #4dafeb; 6 | $blue-lighter: #77c8f8; 7 | $yellow: #f1c40f; 8 | $green: #2ecc71; 9 | $red: #ff5955; 10 | $yellow: #f1c40f; 11 | 12 | $color-1 : #4a2f7e; // purple 13 | $color-2 : #9b59b6; // lilac 14 | $color-3 : #3498db; // blue 15 | $color-4 : #2ecc71; // green 16 | $color-5 : #f1c40f; // yellow 17 | $color-6 : #e67e22; // orange 18 | $color-7 : #d55c4b; // red 19 | $color-8 : #6f4021; // brown 20 | $color-9 : #ffffff; // white 21 | $color-10 : #95a5a6; // grey 22 | $color-11 : #252525; // black 23 | 24 | $black: #111; // black 25 | $darker: #292929; 26 | $dark: #222; // dark 27 | $medium: #888; // medium 28 | $light: #f5f5f5; 29 | $lightish: #eee; // fixme 30 | $lighter: #989898; 31 | $white: #ffffff; 32 | 33 | $sidebar-width: 280px; 34 | 35 | $main-font: Inter; 36 | $sec-font: Inter; 37 | 38 | $font-size: 20px; 39 | $line-height: 1.5em; 40 | 41 | $gutter-a: 10px; 42 | $gutter-b: 20px; 43 | $gutter-c: 40px; 44 | $gutter-d: 60px; 45 | $gutter-e: 80px; 46 | 47 | $folder-gutter: 20px; 48 | -------------------------------------------------------------------------------- /views/partials/tool/zones.html: -------------------------------------------------------------------------------- 1 |

[[__("tool_zones")]]

2 | 3 |
4 |
5 | 8 | 9 | 10 |
11 | 12 |
13 | 14 | 15 | 16 |
17 |
18 | -------------------------------------------------------------------------------- /config/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "team_name": "My Open Spacedeck", 3 | "contact_email": "support@example.org", 4 | 5 | "endpoint": "http://localhost:9666", 6 | "invite_code": "top-sekrit", 7 | 8 | "storage_region": "eu-central-1", 9 | //"storage_bucket": "sdeck-development", 10 | //"storage_cdn": "http://localhost:9123/sdeck-development", 11 | //"storage_endpoint": "http://storage:9000", 12 | 13 | "storage_bucket": "my_spacedeck_bucket", 14 | "storage_cdn": "/storage", 15 | "storage_local_path": "./storage", 16 | 17 | "redis_mock": true, 18 | "mongodb_host": "localhost", 19 | "redis_host": "localhost", 20 | 21 | "google_access" : "", 22 | "google_secret" : "", 23 | "admin_pass": "very_secret_admin_password", 24 | "phantom_api_secret": "very_secret_phantom_password", 25 | 26 | // Choose "console" or "smtp" 27 | "mail_provider": "smtp", 28 | "mail_smtp_host": "your.smtp.host", 29 | "mail_smtp_port": 465, 30 | "mail_smtp_secure": true, 31 | "mail_smtp_require_tls": true, 32 | "mail_smtp_user": "your.smtp.user", 33 | "mail_smtp_pass": "your.secret.smtp.password" 34 | } 35 | -------------------------------------------------------------------------------- /styles/overflow.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | @import "mixins"; 3 | 4 | .overflow-hidden { 5 | overflow: hidden; 6 | } 7 | 8 | .overflow-scroll { 9 | overflow-y: scroll; 10 | overflow-x: scroll; 11 | } 12 | 13 | .overflow-y-scroll { 14 | overflow-y: scroll; 15 | overflow-x: hidden; 16 | } 17 | 18 | .overflow-x-scroll { 19 | overflow-y: hidden; 20 | overflow-x: scroll; 21 | } 22 | 23 | .fit { 24 | position: absolute !important; 25 | width: auto; 26 | height: auto; 27 | right: 0px; 28 | left: 0px; 29 | top: 0px; 30 | bottom: 0px; 31 | } 32 | 33 | .overflow-scroll, 34 | .overflow-hidden, 35 | .overflow-x-scroll, 36 | .overflow-y-scroll { 37 | -webkit-overflow-scrolling: touch; 38 | 39 | &::-webkit-scrollbar { 40 | width: 0px; 41 | height: 0px; 42 | } 43 | &::-webkit-scrollbar-corner,::-webkit-scrollbar { 44 | background-color: rgba(0,0,0,0); 45 | } 46 | 47 | &::-webkit-scrollbar-thumb { 48 | border-radius: 4px; 49 | background-color: rgba(255,255,255,0.1); 50 | -webkit-background-clip: padding-box; 51 | border: 0px solid transparent; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /views/partials/tool/share.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | facebook 7 | 8 | 9 | 10 | twitter 11 | 12 | 13 | 14 | email 15 | 16 |
17 |

Share via Link

18 | {{share_base_url+active_space._id}} 19 |
20 | -------------------------------------------------------------------------------- /routes/api/memberships.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var config = require('config'); 4 | 5 | var async = require('async'); 6 | var url = require("url"); 7 | var path = require("path"); 8 | var crypto = require('crypto'); 9 | var glob = require('glob'); 10 | 11 | var express = require('express'); 12 | var router = express.Router(); 13 | 14 | const db = require('../../models/db'); 15 | const Sequelize = require('sequelize'); 16 | const Op = Sequelize.Op; 17 | const uuidv4 = require('uuid/v4'); 18 | 19 | router.get('/:membership_id/accept', function(req, res, next) { 20 | if (req.user) { 21 | db.Membership.findOne({where:{ 22 | _id: req.params.membership_id, 23 | code: req.query.code 24 | }, include: ['space']}).then((mem) => { 25 | if (mem) { 26 | if (!mem.user) { 27 | mem.state = "active"; 28 | mem.user_id = req.user._id; 29 | 30 | mem.save().then(function() { 31 | res.status(200).json(mem); 32 | }); 33 | } else { 34 | res.status(200).json(mem); 35 | } 36 | } else { 37 | res.status(404).json({"error": "not found"}); 38 | } 39 | }); 40 | } else { 41 | res.sendStatus(403); 42 | } 43 | }); 44 | 45 | module.exports = router; 46 | -------------------------------------------------------------------------------- /views/partials/tool/text-formats.html: -------------------------------------------------------------------------------- 1 |

[[__("text_formats")]]

2 | 3 | 11 | 12 | 21 | -------------------------------------------------------------------------------- /styles/row.scss: -------------------------------------------------------------------------------- 1 | .row:before, 2 | .row:after { 3 | display: table; 4 | content: " "; 5 | } 6 | 7 | .row:after { 8 | clear: both; 9 | } 10 | 11 | .row:before, 12 | .row:after { 13 | display: table; 14 | content: " "; 15 | } 16 | 17 | .row:after { 18 | clear: both; 19 | } 20 | 21 | .row { 22 | margin: 0 -5px; 23 | > div { 24 | position: relative; 25 | min-height: 1px; 26 | padding-right: 5px; 27 | padding-left: 5px; 28 | float: left; 29 | img { 30 | max-width: 100%; 31 | } 32 | } 33 | &.no-gutter { 34 | margin: 0 !important; 35 | > div { 36 | padding: 0px !important; 37 | } 38 | } 39 | 40 | &.cols-12 > div { width: percentage(1 / 12); } 41 | &.cols-11 > div { width: percentage(1 / 11); } 42 | &.cols-10 > div { width: percentage(1 / 10); } 43 | &.cols-9 > div { width: percentage(1 / 9); } 44 | &.cols-8 > div { width: percentage(1 / 8); } 45 | &.cols-7 > div { width: percentage(1 / 7); } 46 | &.cols-6 > div { width: percentage(1 / 6); } 47 | &.cols-5 > div { width: percentage(1 / 5); } 48 | &.cols-4 > div { width: percentage(1 / 4); } 49 | &.cols-3 > div { width: percentage(1 / 3); } 50 | &.cols-2 > div { width: percentage(1 / 2); } 51 | &.cols-1 > div { width: percentage(1 / 1); } 52 | } 53 | -------------------------------------------------------------------------------- /views/partials/tool/selection.html: -------------------------------------------------------------------------------- 1 |
2 |

Selection

3 | 4 |
5 | 9 |
10 | 11 |
12 | 16 |
17 | 18 |
19 | 23 |
24 | 25 |
26 | 30 |
31 |
32 | -------------------------------------------------------------------------------- /styles/smoke.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | 3 | .smoke { 4 | text-align: center; 5 | background-color: white; 6 | box-shadow: 0 0px 20px #666; 7 | } 8 | 9 | .smoke-base { 10 | position: fixed; 11 | top: 0; 12 | left: 0; 13 | bottom: 0; 14 | right: 0; 15 | visibility: hidden; 16 | opacity: 0; 17 | background: rgba(0, 0, 0, 0.3); 18 | } 19 | 20 | .smoke-base.smoke-visible { 21 | opacity: 1; 22 | visibility: visible; 23 | } 24 | 25 | .smokebg { 26 | position: fixed; 27 | top: 0; 28 | left: 0; 29 | bottom: 0; 30 | right: 0; 31 | } 32 | 33 | .smoke-base .smoke-dialog { 34 | margin: auto; 35 | margin-top: 200px; 36 | border-radius: 20px; 37 | 38 | width: 400px; 39 | padding: 10px; 40 | 41 | font-size: 13pt; 42 | text-align: left; 43 | } 44 | 45 | .smoke-dialog-prompt { 46 | margin-top: 15px; 47 | text-align: center; 48 | } 49 | 50 | .smoke-dialog-buttons { 51 | margin: 20px 0 0 0; 52 | } 53 | 54 | .smoke-dialog-buttons button { 55 | background-color: $blue; 56 | color: $light; 57 | margin: 10px 0px 0px 5px; 58 | } 59 | 60 | .smoke-dialog-buttons button.cancel { 61 | background-color: $light; 62 | color: $dark; 63 | } 64 | 65 | .smoke-dialog-prompt input { 66 | width: 75%; 67 | } 68 | 69 | .smoke-base .smoke-dialog-inner { 70 | padding: 20px; 71 | color: #202020; 72 | } 73 | -------------------------------------------------------------------------------- /views/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'layouts/outer.html' %} 2 | 3 | {% block title %}Spacedeck{% endblock %} 4 | 5 | {% block content %} 6 | 7 |
8 |
9 |

Work Together, Visually.

10 |

11 | Whenever you need to lay out pictures, text notes, video and audio clips on a blank canvas, 12 | Spacedeck can help you. 13 |

14 |

15 | 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. 16 |

17 |

18 | Spacedeck is not meant for creating polished designs, but it is a good fit for: 19 |

20 | 27 | Screenshot of Spacedeck 6.0 28 |

29 | The hosted version of Spacedeck 6.0 is currently in beta and invite only. You can also self-host and participate in the open source development. 30 |

31 |
32 |
33 | 34 | {% endblock %} 35 | -------------------------------------------------------------------------------- /views/partials/tool/text-columns.html: -------------------------------------------------------------------------------- 1 |

Columns

2 | 3 |
4 |
5 |
6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 | 14 |
15 |
16 | 17 | 18 | 21 | px 22 |
23 |
24 |
25 | 26 | -------------------------------------------------------------------------------- /styles/pattern.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | @import "mixins"; 3 | 4 | #pattern { 5 | 6 | 7 | #background-position { 8 | height: 210px; 9 | position: relative; 10 | 11 | .position-icon { 12 | position: absolute; 13 | top: 50%; 14 | left: 50%; 15 | width: 120px; 16 | height: 120px; 17 | border: 2px solid $dark; 18 | border-radius: 4px; 19 | margin: -50px -60px; 20 | 21 | .radio { 22 | position: absolute; 23 | display: inline-block; 24 | 25 | &.top-left {top: -30px; left: 0; margin-left: -30px; } 26 | &.top-center {top: -30px; left: 50%; margin-left: -30px; } 27 | &.top-right {top: -30px; right: 0; margin-right: -30px; } 28 | 29 | &.center-left {top: 50%; margin-top: -30px; left: 0; margin-left: -30px; } 30 | &.center-center {top: 50%; margin-top: -30px; left: 50%; margin-left: -30px; } 31 | &.center-right {top: 50%; margin-top: -30px; right: 0; margin-right: -30px; } 32 | 33 | &.bottom-left {bottom: 0; margin-bottom: -30px; left: 0; margin-left: -30px; } 34 | &.bottom-center {bottom: 0; margin-bottom: -30px; left: 50%; margin-left: -30px; } 35 | &.bottom-right {bottom: 0; margin-bottom: -30px; right: 0; margin-right: -30px; } 36 | } 37 | } 38 | 39 | .icon-label { 40 | pointer-events: none; 41 | margin-top: 62px; 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /styles/metrics.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | @import "mixins"; 3 | 4 | #metrics { 5 | #transform-origin { 6 | height: 230px; 7 | position: relative; 8 | 9 | .transform-origin-icon { 10 | position: absolute; 11 | top: 50%; 12 | left: 50%; 13 | width: 120px; 14 | height: 120px; 15 | border: 2px solid rgba(0,0,0,0.1); 16 | border-radius: 4px; 17 | margin: -45px -60px; 18 | 19 | .radio { 20 | position: absolute; 21 | display: inline-block; 22 | 23 | &.top-left {top: -30px; left: 0; margin-left: -30px; } 24 | &.top-center {top: -30px; left: 50%; margin-left: -30px; } 25 | &.top-right {top: -30px; right: 0; margin-right: -30px; } 26 | 27 | &.center-left {top: 50%; margin-top: -30px; left: 0; margin-left: -30px; } 28 | &.center-center {top: 50%; margin-top: -30px; left: 50%; margin-left: -30px; } 29 | &.center-right {top: 50%; margin-top: -30px; right: 0; margin-right: -30px; } 30 | 31 | &.bottom-left {bottom: 0; margin-bottom: -30px; left: 0; margin-left: -30px; } 32 | &.bottom-center {bottom: 0; margin-bottom: -30px; left: 50%; margin-left: -30px; } 33 | &.bottom-right {bottom: 0; margin-bottom: -30px; right: 0; margin-right: -30px; } 34 | } 35 | } 36 | 37 | .icon-label { 38 | pointer-events: none; 39 | margin-top: 62px; 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /views/partials/tool/grid.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | 6 | 9 | px 10 |
11 |
12 | 13 |
14 |
15 | 16 | 17 | 18 | 19 | 20 |
21 |
22 | 23 |
24 | 27 |
28 | 29 |
-------------------------------------------------------------------------------- /views/partials/tool/text-styles.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 7 | 11 | 15 | 19 | 23 | 24 | 28 |
29 |
30 | -------------------------------------------------------------------------------- /styles/form-range.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | @import "mixins"; 3 | 4 | .range-wrapper { 5 | @include user-select(none); 6 | display: inline-block; 7 | position: relative; 8 | width: 100%; 9 | height: 26px; 10 | padding-top: 24px; 11 | } 12 | 13 | input[type='range'] { 14 | display: inline-block; 15 | -webkit-appearance: none !important; 16 | background-color: rgba(0,0,0,0.05); 17 | height: 2px; 18 | border-radius: $radius*10 !important; 19 | outline: 0; 20 | vertical-align: middle; 21 | width: 100%; 22 | } 23 | 24 | input[type='range']::-webkit-slider-thumb { 25 | background-color: $medium; 26 | border-radius: $radius; 27 | -webkit-appearance: none !important; 28 | height:44px; 29 | width:44px; 30 | border-radius: 100%; 31 | background-clip: content-box; 32 | border: 13px solid transparent; 33 | 34 | // &:after { 35 | // content: "100%"; 36 | // display: block; 37 | // } 38 | &:active{ 39 | background-color: $darker !important; 40 | } 41 | } 42 | 43 | .output-wrapper { 44 | position: absolute; 45 | top: 0px; 46 | left: 0px; 47 | right: 60px; 48 | width: auto; 49 | } 50 | 51 | output { 52 | font-size: 11px; 53 | line-height: 1; 54 | background-clip: padding-box; 55 | color: #999; 56 | font-family: Avenir; 57 | position: absolute; 58 | display: inline-block; 59 | top: 0; 60 | right: 0; 61 | pointer-events: none; 62 | vertical-align: middle; 63 | letter-spacing: 0.1em; 64 | } -------------------------------------------------------------------------------- /views/partials/tool/text-align.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 6 | 9 | 12 | 15 | 16 | 17 | 18 | 21 | 24 | 27 |
28 |
29 | -------------------------------------------------------------------------------- /styles/space-profile.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | @import "mixins"; 3 | 4 | #space-profile { 5 | .modal-content {width: 680px; } 6 | } 7 | #item-content { 8 | position: relative; 9 | z-index: 10; 10 | } 11 | #item-cover { 12 | position: absolute; 13 | height: 100%; 14 | width: 100%; 15 | top: 0; 16 | left: 0; 17 | opacity: 0.1; 18 | background-size: cover; 19 | background-position: center; 20 | z-index: 0; 21 | } 22 | .item-head { 23 | .item-title {margin-bottom: 15px; } 24 | > span {display: block; } 25 | } 26 | .item-info { 27 | margin-bottom: 30px; 28 | } 29 | .item-stats { 30 | margin: 0; 31 | padding: 5px 0; 32 | margin-bottom: 20px; 33 | list-style: none; 34 | font-family: $main-font; 35 | font-size: 13px; 36 | 37 | li { 38 | display: inline-block; 39 | .icon { 40 | margin-right: 0px; 41 | } 42 | > span { 43 | margin-right: 10px; 44 | } 45 | } 46 | } 47 | 48 | .item-actions {} 49 | .item-categories, 50 | .item-tags { 51 | padding-top: 20px; 52 | margin-bottom: -20px; 53 | ul { 54 | margin: 0; 55 | padding: 0; 56 | list-style: none; 57 | font-size: 0; 58 | } 59 | 60 | li { 61 | display: inline-block; 62 | margin-right: 5px; 63 | margin-bottom: 10px; 64 | span { 65 | 66 | border: 2px solid rgba(255,255,255,0.1); 67 | display: inline-block; 68 | min-width: 32px; 69 | height: 32px; 70 | line-height: 28px; 71 | font-size: 11px; 72 | border-radius: 100px !important; 73 | padding: 0 10px; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /middlewares/session.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const db = require('../models/db'); 4 | var config = require('config'); 5 | 6 | module.exports = (req, res, next) => { 7 | const token = req.cookies["sdsession"]; 8 | 9 | if (token && token != "null" && token != null) { 10 | db.Session.findOne({where: {token: token}}) 11 | .then(session => { 12 | if (!session) { 13 | // session not found 14 | next(); 15 | } 16 | else db.User.findOne({where: {_id: session.user_id}}) 17 | .then(user => { 18 | if (!user) { 19 | var domain = (process.env.NODE_ENV == "production") ? new URL(config.get('endpoint')).hostname : req.headers.hostname; 20 | res.clearCookie('sdsession', { domain: domain }); 21 | 22 | if (req.accepts("text/html")) { 23 | res.send("Please clear your cookies and try again."); 24 | } else if (req.accepts('application/json')) { 25 | res.status(403).json({ 26 | "error": "token_not_found" 27 | }); 28 | } else { 29 | res.send("Please clear your cookies and try again."); 30 | } 31 | 32 | } else { 33 | req["token"] = token; 34 | req["user"] = user; 35 | next(); 36 | } 37 | }); 38 | }) 39 | .error(err => { 40 | console.error("Session resolve error",err); 41 | next(); 42 | }); 43 | } else { 44 | next(); 45 | } 46 | } 47 | 48 | -------------------------------------------------------------------------------- /styles/rich-text.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | 3 | #rich-text-editor-wrapper { 4 | z-index: 10001; 5 | position: absolute; 6 | left: 0px; 7 | top: 0px; 8 | &:before { 9 | top: 0; 10 | display: block; 11 | content: ""; 12 | height: 100%; 13 | width: 100%; 14 | position: absolute; 15 | pointer-events: none; 16 | border: 1px solid $blue; 17 | z-index: 0; 18 | } 19 | } 20 | 21 | #rich-text-editor-controls { 22 | position: absolute; 23 | width: 100%; 24 | bottom: 100%; 25 | left: 0; 26 | padding-bottom: 10px; 27 | } 28 | 29 | #rich-text-editor { 30 | white-space: normal; 31 | cursor: default; 32 | position: relative; 33 | 34 | // multi-column text 35 | /*&:after{ 36 | width: 32px; 37 | left: 50%; 38 | top: 0; 39 | margin-left: -16px; 40 | border-left: 1px solid $blue; 41 | border-right: 1px solid $blue; 42 | position: absolute; 43 | content: ""; 44 | height: 100%; 45 | pointer-events: none; 46 | }*/ 47 | 48 | li, 49 | p { 50 | cursor: text; 51 | // &:hover{ 52 | // &:after {display: block; } 53 | // } 54 | 55 | // &:after{ 56 | // background-color: rgba(40,140,215,0.05); 57 | // border: 1px solid $blue; 58 | // margin-top: 0.38em; 59 | // margin-bottom: 0.42em; 60 | // position: absolute; 61 | // content: ""; 62 | // width: auto; 63 | // height: auto; 64 | // left: 0; 65 | // right: 0; 66 | // bottom: 0; 67 | // top: 0; 68 | // pointer-events: none; 69 | // } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /styles/login.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | @import "mixins"; 3 | 4 | #account-forms { 5 | z-index: 2000; 6 | width: 100%; 7 | height: 100%; 8 | 9 | &.got-user {display: none; } 10 | 11 | > div { 12 | display: table; 13 | width: 100%; 14 | height: 100%; 15 | // color: white; 16 | position: absolute !important; 17 | pointer-events: none; 18 | 19 | &.active { 20 | &#login form { @include rotateY(0deg); } 21 | &#signup form { @include rotateY(0deg); } 22 | &#password-reset form { @include rotateY(0deg); } 23 | &#password-confirm form { @include rotateY(0deg); } 24 | form { 25 | opacity: 1; 26 | pointer-events: auto; 27 | } 28 | } 29 | 30 | .content { 31 | display: table-cell; 32 | vertical-align: middle; 33 | text-align: center; 34 | -webkit-perspective: 1000; 35 | -moz-perspective: 1000; 36 | -ms-perspective: 1000; 37 | perspective: 1000; 38 | @include perspective-origin(center center); 39 | } 40 | 41 | &#login form { @include rotateY(-180deg); } 42 | &#signup form { @include rotateY(180deg); } 43 | &#password form { @include rotateY(180deg); } 44 | 45 | form { 46 | h4 { 47 | display: inline-block; 48 | } 49 | text-align: left; 50 | display: inline-block; 51 | width: 330px; 52 | opacity: 0; 53 | 54 | @include backface-visibility(hidden); 55 | 56 | @include transition( all 0.25s ease-in-out); 57 | @include transform-origin(center center); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /views/partials/tool/canvas.html: -------------------------------------------------------------------------------- 1 |

Padding

2 | 3 |
4 |
5 |
6 |
7 |
8 | 9 | 10 | 13 | 14 |
15 |
16 |
17 |
18 | 19 | 20 | 23 | 24 |
25 |
26 |
27 |
28 |
-------------------------------------------------------------------------------- /styles/colors.scss: -------------------------------------------------------------------------------- 1 | #fill-color-12 { background-color: transparent !important; 2 | background-image: url('../images/opacity-grid.png'); 3 | background-size: contain; 4 | } 5 | 6 | #fill-color-1 { background-color: #4a2f7e !important;} 7 | #fill-color-2 { background-color: #9b59b6 !important;} 8 | #fill-color-3 { background-color: #3498db !important;} 9 | #fill-color-4 { background-color: #2ecc71 !important;} 10 | #fill-color-5 { background-color: #f1c40f !important;} 11 | #fill-color-6 { background-color: #e67e22 !important;} 12 | #fill-color-7 { background-color: #d55c4b !important;} 13 | #fill-color-8 { background-color: #6f4021 !important;} 14 | #fill-color-9 { background-color: #ffffff !important;} 15 | #fill-color-10 { background-color: #95a5a6 !important;} 16 | #fill-color-11 { background-color: #252525 !important;} 17 | 18 | #stroke-color-12 { background-color: transparent !important; 19 | background-image: url('../images/opacity-grid.png'); 20 | background-size: contain; 21 | } 22 | 23 | #stroke-color-1 { background-color: #4a2f7e !important;} 24 | #stroke-color-2 { background-color: #9b59b6 !important;} 25 | #stroke-color-3 { background-color: #3498db !important;} 26 | #stroke-color-4 { background-color: #2ecc71 !important;} 27 | #stroke-color-5 { background-color: #f1c40f !important;} 28 | #stroke-color-6 { background-color: #e67e22 !important;} 29 | #stroke-color-7 { background-color: #d55c4b !important;} 30 | #stroke-color-8 { background-color: #6f4021 !important;} 31 | #stroke-color-9 { background-color: #ffffff !important;} 32 | #stroke-color-10 { background-color: #95a5a6 !important;} 33 | #stroke-color-11 { background-color: #252525 !important;} 34 | -------------------------------------------------------------------------------- /middlewares/cors.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('../models/db'); 4 | const config = require('config'); 5 | const url = require('url'); 6 | 7 | function respond(origin, req, res, next) { 8 | res.header('Access-Control-Allow-Origin', origin); 9 | res.header('Access-Control-Allow-Credentials', true); 10 | res.header('Access-Control-Max-Age', 60 * 60 * 24); 11 | res.header('Access-Control-Expose-Headers', 'Accepts, Content-Type, X-Spacedeck-Space-Role, X-Spacedeck-Channel, X-Spacedeck-Spacepassword, X-Spacedeck-Auth, X-Spacedeck-Space-Auth'); 12 | res.header('Access-Control-Allow-Headers', 'Accepts, Accept-Language, Accept-Encoding, Accept-Language, Content-Type, X-Spacedeck-Space-Auth, X-Spacedeck-Space-Role, X-Spacedeck-Channel, X-Spacedeck-Spacepassword, X-Spacedeck-Auth'); 13 | res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE'); 14 | 15 | if (req.method == 'OPTIONS') { 16 | res.sendStatus(204); 17 | } else { 18 | next(); 19 | } 20 | } 21 | 22 | module.exports = (req, res, next) => { 23 | const origin = req.headers.origin; 24 | 25 | if (origin) { 26 | const parsedUrl = url.parse(origin, true, true); 27 | 28 | // FIXME 29 | if (parsedUrl.hostname == "cdn.spacedeck.com") { 30 | res.header('Cache-Control', "max-age"); 31 | res.header('Expires', "30d"); 32 | res.removeHeader("Pragma"); 33 | 34 | respond(origin, req, res, next); 35 | } else { 36 | //Team.getTeamForHost(parsedUrl.hostname, (err, team, subdomain) => { 37 | //if (team) { 38 | respond(origin, req, res, next); 39 | //} else { 40 | next(); 41 | //} 42 | //}); 43 | } 44 | 45 | } else { 46 | next(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /styles/ruler.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | @import "mixins"; 3 | 4 | .ruler { 5 | position: absolute; 6 | width: 100%; 7 | border-bottom:1px dotted #e17878; 8 | background-clip: padding-box; 9 | 10 | z-index: 5; 11 | cursor: ns-resize; 12 | .ruler-value { 13 | display: inline-block; 14 | background-color: $blue; 15 | background-color: #e17878; 16 | color: white; 17 | font-size: 11px; 18 | line-height: 1; 19 | position: absolute; 20 | text-align: center; 21 | opacity: 0; 22 | } 23 | &:hover { 24 | .ruler-value { 25 | opacity: 1; 26 | } 27 | } 28 | &.horz { 29 | top: 50px; 30 | width: 100%; 31 | border-bottom:1px dotted #e17878; 32 | height: 1px; 33 | cursor: ns-resize; 34 | &:hover { 35 | border-bottom-style: solid; 36 | } 37 | .ruler-value { 38 | left: 250px; 39 | top: 0; 40 | border-bottom-left-radius: 60px; 41 | border-bottom-right-radius: 60px; 42 | width: 40px; 43 | height: 40px; 44 | line-height: 40px; 45 | } 46 | } 47 | 48 | &.vert { 49 | left: 50px; 50 | height: 100%; 51 | border-left:1px dotted #e17878; 52 | width: 1px; 53 | cursor: ew-resize; 54 | &:hover {border-left-style: solid; } 55 | .ruler-value { 56 | left: 0; 57 | top: 250px; 58 | border-top-right-radius: 60px; 59 | border-bottom-right-radius: 60px; 60 | height: 40px; 61 | width: 40px; 62 | line-height: 40px; 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /middlewares/api_helpers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('../models/db'); 4 | var config = require('config'); 5 | const redis = require('../helpers/redis'); 6 | 7 | // FIXME TODO object.toJSON() 8 | 9 | var saveAction = (actionKey, object) => { 10 | if (object.constructor.modelName == "Space") 11 | return; 12 | 13 | let attr = { 14 | action: actionKey, 15 | space: object.space_id || object.space, 16 | user: object.user_id || object.user, 17 | editor_name: object.editor_name, 18 | object: object 19 | }; 20 | 21 | /*let action = new Action(attr); 22 | action.save(function(err) { 23 | if (err) 24 | console.error("saved create action err:", err); 25 | });*/ 26 | }; 27 | 28 | module.exports = (req, res, next) => { 29 | res.header("Cache-Control", "no-cache"); 30 | 31 | req['channelId'] = req.headers['x-spacedeck-channel']; 32 | req['spacePassword'] = req.headers['x-spacedeck-spacepassword']; 33 | req['spaceAuth'] = req.query['spaceAuth'] || req.headers['x-spacedeck-space-auth']; 34 | 35 | res['distributeCreate'] = function(model, object) { 36 | if (!object) return; 37 | redis.sendMessage("create", model, object, req.channelId); 38 | this.status(201).json(object); 39 | saveAction("create", object); 40 | }; 41 | 42 | res['distributeUpdate'] = function(model, object) { 43 | if (!object) return; 44 | redis.sendMessage("update", model, object, req.channelId); 45 | this.status(200).json(object); 46 | saveAction("update", object); 47 | }; 48 | 49 | res['distributeDelete'] = function(model, object) { 50 | if (!object) return; 51 | redis.sendMessage("delete", model, object, req.channelId); 52 | this.sendStatus(204); 53 | saveAction("delete", object); 54 | }; 55 | 56 | next(); 57 | } 58 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spacedeck-open", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node spacedeck.js" 7 | }, 8 | "engines": { 9 | "node": ">=10.0.0" 10 | }, 11 | "dependencies": { 12 | "archiver": "1.3.0", 13 | "async": "2.3.0", 14 | "basic-auth": "1.1.0", 15 | "bcryptjs": "2.4.3", 16 | "body-parser": "^1.19.0", 17 | "cheerio": "0.22.0", 18 | "config": "1.25.1", 19 | "cookie-parser": "~1.4.3", 20 | "execSync": "latest", 21 | "express": "^4.16.4", 22 | "file-type": "^7.6.0", 23 | "glob": "7.1.1", 24 | "gm": "^1.23.1", 25 | "gulp": "^4.0.2", 26 | "gulp-concat": "^2.6.1", 27 | "gulp-sass": "^4.0.2", 28 | "helmet": "^3.5.0", 29 | "i18n-2": "0.6.3", 30 | "log-timestamp": "latest", 31 | "mock-aws-s3": "^2.6.0", 32 | "moment": "^2.19.3", 33 | "morgan": "^1.9.1", 34 | "node-phantom-simple": "2.2.4", 35 | "node-server-screenshot": "^0.2.1", 36 | "nodemailer": "^4.6.7", 37 | "phantomjs-prebuilt": "^2.1.16", 38 | "read-chunk": "^2.1.0", 39 | "request": "^2.88.0", 40 | "sanitize-html": "^1.11.1", 41 | "sequelize": "^4.37.6", 42 | "serve-favicon": "~2.4.2", 43 | "serve-static": "^1.13.1", 44 | "slug": "^1.1.0", 45 | "sqlite3": "^4.0.0", 46 | "swig": "1.4.2", 47 | "umzug": "^2.1.0", 48 | "underscore": "1.8.3", 49 | "uuid": "^3.2.1", 50 | "validator": "7.0.0", 51 | "ws": "3.3.1" 52 | }, 53 | "main": "app.js", 54 | "description": "", 55 | "directories": {}, 56 | "repository": { 57 | "type": "git", 58 | "url": "https://github.com/spacedeck/spacedeck-open.git" 59 | }, 60 | "keywords": [], 61 | "author": "Lukas F. Hartmann, Martin Güther", 62 | "license": "AGPLv3" 63 | } 64 | -------------------------------------------------------------------------------- /styles/pages.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | @import "mixins"; 3 | 4 | #pages { 5 | #pages-actions { 6 | display: inline-block; 7 | padding-left: 25px; 8 | margin: 20px 0; 9 | } 10 | 11 | #pages-options { 12 | position: relative; 13 | display: inline-block; 14 | margin-right: -4px; 15 | padding-right: 0px; 16 | margin: 10px 0; 17 | 18 | } 19 | 20 | .pages-wrapper { 21 | counter-reset: pages-counter; 22 | } 23 | 24 | .page { 25 | counter-increment: pages-counter; 26 | display: block; 27 | position: relative; 28 | 29 | .dropdown { 30 | position: absolute; 31 | bottom: 0; 32 | right: -15px; 33 | margin: 10px 25px; 34 | } 35 | 36 | &.active { 37 | img { 38 | border: 2px solid white; 39 | } 40 | .page-thumbnail:before { 41 | background-color: white; 42 | } 43 | } 44 | 45 | .page-thumbnail { 46 | position: relative; 47 | width: 100%; 48 | margin-top: 25px; 49 | padding: 0 25px; 50 | 51 | img { 52 | border-radius: $radius; 53 | display: block; 54 | width: 100%; 55 | } 56 | 57 | &:before { 58 | z-index: 100; 59 | content: counter(pages-counter); 60 | right: 0px; 61 | margin-right: 5px; 62 | position: absolute; 63 | top: 50%; 64 | left: 50%; 65 | font-size: 15px; 66 | background-color: white; 67 | border-radius: 100%; 68 | margin: -15px; 69 | line-height: 30px; 70 | width: 30px; 71 | height: 30px; 72 | text-align: center; 73 | 74 | background-color: $light; 75 | background-color: $dark; 76 | color: $medium;; 77 | } 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /views/partials/modal/folder-settings.html: -------------------------------------------------------------------------------- 1 | 44 | -------------------------------------------------------------------------------- /styles/header.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | @import "mixins"; 3 | 4 | .header-center, 5 | .header-left, 6 | .header-right { 7 | position: absolute; 8 | @include backface-visibility(hidden); 9 | z-index: 3000; 10 | top: 10px; 11 | 12 | > * { 13 | display: inline-block; 14 | vertical-align: middle; 15 | } 16 | 17 | &.out { 18 | opacity: 0; 19 | pointer-events: none; 20 | * { 21 | pointer-events: none !important; 22 | } 23 | } 24 | } 25 | 26 | .home { 27 | margin-top: -20px; 28 | margin-left: -20px; 29 | } 30 | 31 | .header-left { 32 | left: 0; 33 | padding-left: 10px; 34 | padding-left: 20px; 35 | padding-top: 20px; 36 | } 37 | .header-right { 38 | right: 0; 39 | padding-right: 20px; 40 | padding-top: 20px; 41 | } 42 | 43 | .header-center { 44 | width: 100%; 45 | left: 0; 46 | right: 0; 47 | position: absolute; 48 | text-align: center; 49 | color: $medium; 50 | > * { 51 | font-size: 20px; 52 | line-height: 44px; 53 | color: $medium; 54 | padding: 0 10px; 55 | } 56 | } 57 | .header-left > * { margin-right: 10px; } 58 | .header-right > * { margin-left: 10px; } 59 | .header-right { font-size: 0;} 60 | 61 | .title { 62 | width: 100%; 63 | left: 0; 64 | position: absolute; 65 | text-align: center; 66 | pointer-events: none; 67 | 68 | h1 { 69 | margin: 0; 70 | height: 60px; 71 | line-height: 60px; 72 | font-size: $font-size*0.75; 73 | // text-transform: uppercase; 74 | font-weight: bold; 75 | color: $medium; 76 | display: inline-block; 77 | margin-top: -14px; 78 | pointer-events: all; 79 | font-weight: normal; 80 | } 81 | } 82 | 83 | .author { 84 | float: left; 85 | .btn { 86 | margin-right: 10px; 87 | } 88 | .author-date { 89 | opacity: 0.5; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /views/partials/tool/object-options.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 7 | 8 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | 31 | 35 | 36 |
37 |
38 | -------------------------------------------------------------------------------- /styles/icon.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | @import "mixins"; 3 | 4 | .number, 5 | .letter { 6 | height: 60px; 7 | width: 60px; 8 | display: inline-block; 9 | 10 | font-family: $main-font; 11 | font-size: 30px; 12 | font-size: 25px; 13 | line-height: 60px; 14 | text-align: center; 15 | font-style: normal; 16 | font-weight: bold; 17 | letter-spacing: -0.04em; 18 | 19 | vertical-align: middle; 20 | -webkit-font-smoothing: antialiased; 21 | -moz-osx-font-smoothing: grayscale; 22 | @include transition(all 0s 0s ease-in-out); 23 | white-space: nowrap; 24 | span {font-weight: normal; opacity: 0.5;} 25 | &.number-md { 26 | font-size: 16px; 27 | height: 44px; 28 | width: 44px; 29 | line-height: 43px; 30 | } 31 | } 32 | 33 | .btn-md .letter { 34 | font-size: 16px; 35 | height: 44px; 36 | width: 44px; 37 | line-height: 43px; 38 | } 39 | 40 | 41 | .icon { 42 | position: relative; 43 | @include icon; 44 | } 45 | 46 | 47 | .icon-soft { 48 | opacity: 0.5; 49 | } 50 | 51 | .icon-xxs { 52 | @include icon-xxs; 53 | } 54 | 55 | .icon-xs { 56 | @include icon-xs; 57 | } 58 | 59 | .icon-sm { 60 | @include icon-sm; 61 | } 62 | 63 | .icon-md { 64 | @include icon-md; 65 | } 66 | 67 | .icon-xl { 68 | @include icon-xl; 69 | } 70 | 71 | .icon-xxl { 72 | @include icon-xxl; 73 | } 74 | 75 | .rot45 > .icon { 76 | transform: rotateZ(45deg); 77 | transition: all 0.1s 0s ease-in-out !important; 78 | } 79 | 80 | .rot45 > .icon::before { 81 | transition: all 0.1s 0s ease-in-out !important; 82 | } 83 | 84 | .rot45:hover > .icon { 85 | transform: rotateZ(45deg) translateX(-8px); 86 | } 87 | 88 | .icon-svg { 89 | background-size: 26px; 90 | background-position: center; 91 | background-repeat: no-repeat; 92 | } 93 | 94 | .icon-sd6 { 95 | background-image: url(/images/sd6-icon-white.svg); 96 | } 97 | -------------------------------------------------------------------------------- /styles/files.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | @import "mixins"; 3 | 4 | 5 | #files-empty, 6 | #favorites-empty { 7 | position: absolute; 8 | top: 0px; 9 | bottom: 0px; 10 | width: 100%; 11 | height: 100%; 12 | display: table; 13 | pointer-events: none; 14 | color: $medium; 15 | > div { 16 | display: table-cell; 17 | vertical-align: middle; 18 | text-align: center; 19 | padding: 20px; 20 | .btn { 21 | pointer-events: auto; 22 | } 23 | } 24 | } 25 | 26 | .files-wrapper { 27 | overflow: hidden; 28 | position: absolute; 29 | top: 120px; 30 | bottom: 0px; 31 | width: 100%; 32 | height: auto; 33 | } 34 | 35 | .in-from-left, 36 | .in-from-right, 37 | .out-to-left, 38 | .out-to-right { 39 | 40 | @include backface-visibility(hidden); 41 | 42 | @include animation-duration(0.225s); 43 | @include animation-iteration-count(1); 44 | @include animation-timing-function(ease-out); 45 | @include animation-fill-mode(forwards); 46 | // overflow: hidden !important; 47 | } 48 | 49 | .in-from-left { @include animation-name(in-from-left); } 50 | .in-from-right { @include animation-name(in-from-right); } 51 | .out-to-left { @include animation-name(out-to-left); } 52 | .out-to-right { @include animation-name(out-to-right); } 53 | 54 | @-webkit-keyframes in-from-left { 55 | from { @include translateX (-100%); opacity: 0;} 56 | to { @include translateX (0%); opacity: 1; } 57 | } 58 | 59 | @-webkit-keyframes in-from-right { 60 | from { @include translateX (100%); opacity: 0; } 61 | to { @include translateX (0%); opacity: 1; } 62 | } 63 | 64 | @-webkit-keyframes out-to-left { 65 | from { @include translateX (0%); opacity: 1; } 66 | to { @include translateX (-100%); } 67 | } 68 | 69 | @-webkit-keyframes out-to-right { 70 | from { @include translateX (0%); opacity: 1; } 71 | to { @include translateX (100%); opacity: 0; } 72 | } -------------------------------------------------------------------------------- /styles/guides.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | @import "mixins"; 3 | 4 | $guide-gutter: 50px !default; 5 | 6 | .space-guides { 7 | position:absolute; 8 | height:auto; 9 | width: auto; 10 | z-index: 5000; 11 | pointer-events: none !important; 12 | 13 | top: 0px; 14 | left: 0px; 15 | right: 0px; 16 | bottom: 0px; 17 | 18 | // compensate for border spacing 19 | // width: auto; 20 | // left: -50px; 21 | // right: -50px; 22 | 23 | table { 24 | @include box-sizing(content-box); 25 | width: 100%; // space width + (border spacing * 2) 26 | height:100%; 27 | 28 | position:absolute; 29 | table-layout: auto; 30 | border-collapse: separate; 31 | border-spacing: $guide-gutter 0px; 32 | // border-collapse: collapse; 33 | // border-spacing: 0px 0px; 34 | 35 | .column td { 36 | border:1px solid rgba(40,140,215,0.6);; 37 | // background-color:rgba(40,140,215,0.125); 38 | &:first-child { 39 | // border-left:1px solid #448afe; 40 | } 41 | // -webkit-box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.075), 0 0 10px rgba(255,255,255, 0.075); 42 | } 43 | 44 | .margin-top td, 45 | .margin-bottom td { 46 | // background-color:rgba(40,140,215,0.1); 47 | border-top:none; 48 | border-bottom:none; 49 | border-right:1px solid rgba(40,140,215,0.125); 50 | border-left:1px solid rgba(40,140,215,0.125);; 51 | height: $guide-gutter; 52 | } 53 | 54 | &.no-gutter .margin-top td:first-child, 55 | &.no-gutter .margin-bottom td:first-child { 56 | border-left:1px solid rgba(40,140,215,0.125); 57 | } 58 | 59 | &.no-gutter .column td, 60 | &.no-gutter .margin-top td, 61 | &.no-gutter .margin-bottom td{ 62 | border-left:none; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /views/partials/meta-folder.html: -------------------------------------------------------------------------------- 1 | 56 | -------------------------------------------------------------------------------- /helpers/mailer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const config = require('config'); 4 | const nodemailer = require('nodemailer'); 5 | const swig = require('swig'); 6 | //var AWS = require('aws-sdk'); 7 | 8 | module.exports = { 9 | sendMail: (to_email, subject, body, options) => { 10 | if (!options) { 11 | options = {}; 12 | } 13 | 14 | const teamname = options.teamname || config.get('team_name'); 15 | const from = teamname + ' <' + config.get('contact_email') + '>'; 16 | 17 | let reply_to = [from]; 18 | if (options.reply_to) { 19 | reply_to = [options.reply_to]; 20 | } 21 | 22 | let plaintext = body; 23 | if (options.action && options.action.link) { 24 | plaintext+="\n"+options.action.link+"\n\n"; 25 | } 26 | 27 | const htmlText = swig.renderFile('./views/emails/action.html', { 28 | text: body.replace(/(?:\n)/g, '
'), 29 | options: options 30 | }); 31 | 32 | if (config.get('mail_provider') === 'console') { 33 | 34 | console.log("Email: to " + to_email + " in production.\nreply_to: " + reply_to + "\nsubject: " + subject + "\nbody: \n" + htmlText + "\n\n plaintext:\n" + plaintext); 35 | 36 | } else if (config.get('mail_provider') === 'smtp') { 37 | 38 | const transporter = nodemailer.createTransport({ 39 | host: config.get('mail_smtp_host'), 40 | port: config.get('mail_smtp_port'), 41 | secure: config.get('mail_smtp_secure'), 42 | requireTLS: config.get('mail_smtp_require_tls'), 43 | auth: { 44 | user: config.get('mail_smtp_user'), 45 | pass: config.get('mail_smtp_pass'), 46 | } 47 | }); 48 | 49 | transporter.sendMail({ 50 | from: from, 51 | replyTo: reply_to, 52 | to: to_email, 53 | subject: subject, 54 | text: plaintext, 55 | html: htmlText, 56 | }, function(err, info) { 57 | if (err) { 58 | console.error("Error sending email:", err); 59 | } else { 60 | console.log("Email sent."); 61 | } 62 | }); 63 | 64 | } 65 | } 66 | }; 67 | -------------------------------------------------------------------------------- /styles/margin-columns.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | @import "mixins"; 3 | 4 | #margins { 5 | .btn-more, 6 | .btn-less { 7 | &:active .icon { 8 | opacity: 1; 9 | } 10 | .icon { 11 | color: white; 12 | opacity: 0.2; 13 | } 14 | } 15 | 16 | 17 | #margin { 18 | height: 200px; 19 | position: relative; 20 | 21 | .btn-toggle { 22 | margin: -22px; 23 | } 24 | 25 | label { 26 | pointer-events: none; 27 | margin-top: 62px; 28 | } 29 | 30 | .margin-icon { 31 | position: absolute; 32 | top: 50%; 33 | left: 50%; 34 | width: 140px; 35 | height: 140px; 36 | border: 2px solid rgba(0,0,0,0.1); 37 | border-radius: 4px; 38 | margin: -70px -70px; 39 | 40 | > div { 41 | position: absolute; 42 | display: inline-block; 43 | height: 30px; 44 | width: 30px; 45 | 46 | .input { 47 | padding: 0px; 48 | width: 30px; 49 | height: 30px; 50 | line-height: 30px; 51 | border: none; 52 | font-size: 22px; 53 | text-align: center; 54 | display: block; 55 | background-color: $light; 56 | } 57 | 58 | 59 | &.top { 60 | top: -15px; left: 50%; margin-left: -15px; 61 | 62 | .btn-more { right: -30px; top: 15px; } 63 | .btn-less { left: -30px; top: 15px; } 64 | } 65 | 66 | &.bottom { 67 | bottom: -15px; left: 50%; margin-left: -15px; 68 | 69 | .btn-more { right: -30px; bottom: 15px; } 70 | .btn-less { left: -30px; bottom: 15px; } 71 | } 72 | 73 | &.left { 74 | left: -16px; top: 50%; margin-top: -15px; 75 | 76 | .btn-more { left: 15px; top: -30px; } 77 | .btn-less { left: 15px; bottom: -30px; } 78 | } 79 | 80 | &.right { 81 | right: -16px; top: 50%; margin-top: -15px; 82 | 83 | .btn-more { right: 15px; top: -30px; } 84 | .btn-less { right: 15px; bottom: -30px; } 85 | } 86 | } 87 | } 88 | 89 | } 90 | } -------------------------------------------------------------------------------- /models/migrations/01-spaces-delete-cascade.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: function(migration, DataTypes) { 5 | return Promise.all([ 6 | migration.changeColumn('memberships', 'space_id', 7 | { 8 | type: DataTypes.STRING, 9 | references: { 10 | model: 'spaces', 11 | key: '_id' 12 | }, 13 | onDelete: 'CASCADE', 14 | onUpdate: 'CASCADE' 15 | } 16 | ), 17 | migration.changeColumn('artifacts', 'space_id', 18 | { 19 | type: DataTypes.STRING, 20 | references: { 21 | model: 'spaces', 22 | key: '_id' 23 | }, 24 | onDelete: 'CASCADE', 25 | onUpdate: 'CASCADE' 26 | } 27 | ), 28 | migration.changeColumn('messages', 'space_id', 29 | { 30 | type: DataTypes.STRING, 31 | references: { 32 | model: 'spaces', 33 | key: '_id' 34 | }, 35 | onDelete: 'CASCADE', 36 | onUpdate: 'CASCADE' 37 | } 38 | ) 39 | ]) 40 | }, 41 | 42 | down: function(migration, DataTypes) { 43 | return Promise.all([ 44 | migration.changeColumn('memberships', 'space_id', 45 | { 46 | type: DataTypes.STRING, 47 | references: { 48 | model: 'spaces', 49 | key: '_id' 50 | }, 51 | onDelete: 'CASCADE', 52 | onUpdate: 'NO ACTION' 53 | } 54 | ), 55 | migration.changeColumn('artifacts', 'space_id', 56 | { 57 | type: DataTypes.STRING, 58 | references: { 59 | model: 'spaces', 60 | key: '_id' 61 | }, 62 | onDelete: 'CASCADE', 63 | onUpdate: 'NO ACTION' 64 | } 65 | ), 66 | migration.changeColumn('messages', 'space_id', 67 | { 68 | type: DataTypes.STRING, 69 | references: { 70 | model: 'spaces', 71 | key: '_id' 72 | }, 73 | onDelete: 'CASCADE', 74 | onUpdate: 'NO ACTION' 75 | } 76 | ) 77 | ]) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /styles/space-guides.scss: -------------------------------------------------------------------------------- 1 | $guide-gutter: 50px !default; 2 | #space-helpers { 3 | pointer-events:none; 4 | position:absolute; 5 | width:100%; 6 | height:100%; 7 | top:0; 8 | left:0; 9 | // display: none; 10 | 11 | .guides { 12 | position:absolute; 13 | height:100%; 14 | width: 100%; 15 | top:0; 16 | z-index: 5000; 17 | pointer-events: none !important; 18 | 19 | // compensate for border spacing 20 | // width: auto; 21 | // left: -50px; 22 | // right: -50px; 23 | 24 | table { 25 | width: 100%; // space width + (border spacing * 2) 26 | height:100%; 27 | position:absolute; 28 | table-layout: auto; 29 | border-collapse: separate; 30 | border-spacing: $guide-gutter 0px; 31 | // border-collapse: collapse; 32 | // border-spacing: 0px 0px; 33 | 34 | .column td { 35 | border:1px solid rgba(40,140,215,0.6);; 36 | // background-color:rgba(40,140,215,0.125); 37 | &:first-child { 38 | // border-left:1px solid #448afe; 39 | } 40 | // -webkit-box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.075), 0 0 10px rgba(255,255,255, 0.075); 41 | } 42 | 43 | .margin-top td, 44 | .margin-bottom td { 45 | // background-color:rgba(40,140,215,0.1); 46 | border-top:none; 47 | border-bottom:none; 48 | border-right:1px solid rgba(40,140,215,0.125); 49 | border-left:1px solid rgba(40,140,215,0.125);; 50 | height: $guide-gutter; 51 | } 52 | 53 | &.no-gutter .margin-top td:first-child, 54 | &.no-gutter .margin-bottom td:first-child { 55 | border-left:1px solid rgba(40,140,215,0.125); 56 | } 57 | 58 | &.no-gutter .column td, 59 | &.no-gutter .margin-top td, 60 | &.no-gutter .margin-bottom td{ 61 | border-left:none; 62 | } 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /styles/annotation.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | @import "mixins"; 3 | 4 | .article-annotations { 5 | display: none; 6 | .disclaimer { 7 | opacity: 0.5; 8 | font-size: 13px; 9 | line-height: 20px; 10 | } 11 | 12 | position: absolute; 13 | top: 0; 14 | left: 100%; 15 | font-size: 13px; 16 | line-height: 20px; 17 | margin-left: 100px; 18 | 19 | [contentEditable=true]{font-size: 13px; line-height: 20px; } 20 | 21 | .annotation-item-body, 22 | .annotation-reply-body { 23 | /* Non standard for webkit */ 24 | word-break: break-word; 25 | 26 | -webkit-hyphens: auto; 27 | -moz-hyphens: auto; 28 | hyphens: auto; 29 | } 30 | 31 | .annotation-group { 32 | width: 250px; 33 | margin-top: -2px; 34 | .annotation-group-toggle { 35 | position: absolute; 36 | top: -15px; 37 | right: 100%; 38 | margin-right: 20px; 39 | opacity: 0.25; 40 | } 41 | 42 | .annotation-item { 43 | // border-bottom: 1px solid rgba(0,0,0,0.05); 44 | margin-bottom: $gutter-a/3; 45 | padding-bottom: $gutter-a/4; 46 | } 47 | .annotation-create-item, 48 | .annotation-item { 49 | 50 | .annotation-replies, 51 | .annotation-item-body { 52 | position: relative; 53 | margin-left: 44px; 54 | } 55 | 56 | .annotation-link { position: absolute; right: 0; top: 0; margin: -7px; opacity: 0.5;} 57 | 58 | .annotation-author { 59 | position: relative; 60 | color: $dark; 61 | font-weight: bold; 62 | img { position: absolute; top: 0; right: 100%; margin-right: 11px; margin-top: 2px; pointer-events: none;} 63 | } 64 | 65 | .annotation-replies { 66 | font-size: 11px; 67 | line-height: 15px; 68 | 69 | .annotation-create-reply, 70 | .annotation-reply { 71 | margin-bottom: $gutter-a/3; 72 | 73 | &:first-child {margin-top: $gutter-a/3; } 74 | &:last-of-type {margin-bottom: 0px; } 75 | 76 | .annotation-reply-body { 77 | position: relative; 78 | margin-left: 37px; 79 | } 80 | } 81 | } 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /helpers/phantom.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const db = require('../models/db'); 4 | const config = require('config'); 5 | const phantom = require('node-phantom-simple'); 6 | const os = require('os'); 7 | 8 | module.exports = { 9 | // type = "pdf" or "png" 10 | takeScreenshot: function(space,type,on_success,on_error) { 11 | var spaceId = space._id; 12 | var space_url = config.get("endpoint")+"/api/spaces/"+spaceId+"/html"; 13 | 14 | var export_path = os.tmpdir()+"/"+spaceId+"."+type; 15 | 16 | var timeout = 5000; 17 | if (type=="pdf") timeout = 30000; 18 | 19 | space_url += "?api_token="+config.get("phantom_api_secret"); 20 | 21 | console.log("[space-screenshot] url: "+space_url); 22 | console.log("[space-screenshot] export_path: "+export_path); 23 | 24 | var on_success_called = false; 25 | 26 | var on_exit = function(exit_code) { 27 | if (exit_code>0) { 28 | console.error("phantom abnormal exit for url "+space_url); 29 | if (!on_success_called && on_error) { 30 | on_error(); 31 | } 32 | } 33 | }; 34 | 35 | phantom.create({ path: require('phantomjs-prebuilt').path }, function (err, browser) { 36 | if (err) { 37 | console.error(err); 38 | } else { 39 | return browser.createPage(function (err, page) { 40 | console.log("page created, opening ",space_url); 41 | 42 | if (type=="pdf") { 43 | var psz = { 44 | width: space.width+"px", 45 | height: space.height+"px" 46 | }; 47 | page.set('paperSize', psz); 48 | } 49 | 50 | page.set('settings.resourceTimeout',timeout); 51 | page.set('settings.javascriptEnabled',false); 52 | 53 | return page.open(space_url, function (err,status) { 54 | page.render(export_path, function() { 55 | on_success_called = true; 56 | if (on_success) { 57 | on_success(export_path); 58 | } 59 | page.close(); 60 | browser.exit(); 61 | }); 62 | }); 63 | }); 64 | } 65 | 66 | }, { 67 | onExit: on_exit 68 | }); 69 | } 70 | }; 71 | -------------------------------------------------------------------------------- /styles/members.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | @import "mixins"; 3 | 4 | #editors, #editors-list { 5 | @include user-select(none); 6 | 7 | h6 { 8 | padding: 15px 25px; 9 | margin: 0px; 10 | color: $medium; 11 | font-size: 10px; 12 | } 13 | 14 | .overflow-y-scroll { 15 | top: 60px !important; 16 | } 17 | 18 | ul { 19 | padding: 0px; 20 | margin: 0px; 21 | } 22 | } 23 | 24 | 25 | 26 | .editor > span { 27 | border-radius: $radius; 28 | display: block; 29 | // background-color: rgba(255,255,255,0.05); 30 | position: relative; 31 | padding: 11px 25px; 32 | padding-left: 45px !important; 33 | padding-right: 0px !important; 34 | min-height: 60px; 35 | border: none; 36 | 37 | .editor-avatar { 38 | height: 34px; 39 | width: 34px; 40 | line-height: 34px; 41 | left: 0px; 42 | top: 10px; 43 | 44 | background-color: $dark; 45 | color: $darker ; 46 | color: white; 47 | font-weight: 700; 48 | 49 | font-size: 15px; 50 | font-family: $main-font; 51 | 52 | display: inline-block; 53 | text-align: center; 54 | border-radius: $radius; 55 | background-size: cover; 56 | background-position: center; 57 | position: absolute; 58 | &.status-off, 59 | &.status-on { 60 | &:before { 61 | top: 0; 62 | right: 0; 63 | display: block; 64 | position: absolute; 65 | color: $darker ; 66 | border: 1px solid #292929; 67 | border-radius: 100px; 68 | 69 | padding: 4px; 70 | font-weight: 700; 71 | text-transform: uppercase; 72 | font-size: 0px; 73 | height: 4px; 74 | width: 4px; 75 | } 76 | } 77 | &.status-off:before {background-color: #d55c4b; content: "off"; display: none;} 78 | &.status-on:before {background-color: #2ecc71; content: "on"; } 79 | } 80 | 81 | .editor-email, 82 | .editor-name { 83 | font-family: $main-font; 84 | font-size: 11px; 85 | line-height: 1.3; 86 | display: block; 87 | font-weight: 300; 88 | overflow: hidden; 89 | text-overflow: ellipsis; 90 | white-space: nowrap; 91 | color: $medium; 92 | } 93 | .editor-email { 94 | opacity: 0.5; 95 | } 96 | } -------------------------------------------------------------------------------- /views/layouts/outer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Spacedeck Open – {% block title %}{% endblock %} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 | 22 |
23 | 24 |
25 | {% if !user %} 26 | [[__("login")]] 27 | [[__("signup")]] 28 | {% else %} 29 | [[__("spaces")]] 30 | [[__("logout")]] 31 | {% endif %} 32 | 33 |
34 |
35 | 36 | {% block content %}{% endblock %} 37 | 38 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /views/partials/tool/shapes.html: -------------------------------------------------------------------------------- 1 |

[[__("tool_shape")]]

2 | 3 |
4 |
5 |
6 | 10 | 11 | 15 | 16 | 20 | 21 | 25 | 26 | 30 | 31 | 35 | 36 | 40 | 41 | 45 | 46 |
47 |
48 |
49 | -------------------------------------------------------------------------------- /routes/api/webgrabber.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var config = require('config'); 4 | require('../../models/db'); 5 | 6 | var fs = require('fs'); 7 | var phantom = require('node-phantom-simple'); 8 | var md5 = require('md5'); 9 | 10 | var express = require('express'); 11 | var router = express.Router(); 12 | 13 | function website_to_png(url,on_success,on_error) { 14 | var hash = md5(url); 15 | var export_path = "/tmp/webgrabber-"+hash+".png"; 16 | 17 | var timeout = 2000; 18 | 19 | console.log("[webgrabber] url: "+url); 20 | console.log("[webgrabber] export_path: "+export_path); 21 | 22 | var on_success_called = false; 23 | 24 | var on_exit = function(exit_code) { 25 | if (exit_code>0) { 26 | console.log("[phantom-webgrabber] abnormal exit for url "+url); 27 | if (!on_success_called && on_error) { 28 | on_error(); 29 | } 30 | } 31 | }; 32 | 33 | fs.stat(export_path, function(err, stat) { 34 | if (!err) { 35 | // file exists 36 | console.log("[webgrabber] serving cached snapshot of url: "+url); 37 | on_success(export_path); 38 | } else { 39 | phantom.create({ path: require('phantomjs-prebuilt').path }, function (err, browser) { 40 | return browser.createPage(function (err, page) { 41 | page.set('settings.resourceTimeout',timeout); 42 | page.set('settings.javascriptEnabled',false); 43 | 44 | return page.open(url, function(err, status) { 45 | console.log("[webgrabber] status: "+status); 46 | page.render(export_path, function() { 47 | on_success_called = true; 48 | on_success(export_path); 49 | browser.exit(); 50 | }); 51 | }); 52 | }); 53 | }, { 54 | onExit: on_exit 55 | }); 56 | } 57 | }); 58 | } 59 | 60 | router.get('/:id', function (req, res) { 61 | var uri = new Buffer(req.params.id, "base64")+""; 62 | var on_success_called = false; 63 | 64 | website_to_png(uri, (image_path) => { 65 | on_success_called = true; 66 | res.sendFile(image_path); 67 | }, () => { 68 | if (!on_success_called) { 69 | res.status(500).send("[webgrabber] Error fetching website."); 70 | } 71 | }); 72 | }); 73 | 74 | module.exports = router; 75 | -------------------------------------------------------------------------------- /views/partials/tool/toolbar-object-options.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |
6 | 10 |
11 | 12 |
13 | 17 |
18 | 19 |
20 | 24 |
25 |
26 | 27 |
28 | 32 |
33 | 34 | 38 |
39 | 40 |
41 | 45 |
46 |
47 | -------------------------------------------------------------------------------- /routes/api/sessions.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var config = require('config'); 4 | const db = require('../../models/db'); 5 | 6 | var bcrypt = require('bcryptjs'); 7 | var crypto = require('crypto'); 8 | var URL = require('url').URL; 9 | 10 | var express = require('express'); 11 | var router = express.Router(); 12 | 13 | router.post('/', function(req, res) { 14 | var data = req.body; 15 | if (!data.email || !data.password) { 16 | res.status(400).json({}); 17 | return; 18 | } 19 | 20 | var email = req.body.email.toLowerCase(); 21 | var password = req.body["password"]; 22 | 23 | db.User.findOne({where: {email: email}}) 24 | .error(err => { 25 | res.sendStatus(404); 26 | }) 27 | .then(user => { 28 | if (!user) { 29 | res.sendStatus(404); 30 | } 31 | else if (bcrypt.compareSync(password, user.password_hash)) { 32 | crypto.randomBytes(48, function(ex, buf) { 33 | var token = buf.toString('hex'); 34 | 35 | var session = { 36 | user_id: user._id, 37 | token: token, 38 | ip: req.ip, 39 | device: "web", 40 | created_at: new Date() 41 | }; 42 | 43 | db.Session.create(session) 44 | .error(err => { 45 | console.error("Error creating Session:",err); 46 | res.sendStatus(500); 47 | }) 48 | .then(() => { 49 | var domain = (process.env.NODE_ENV == "production") ? new URL(config.get('endpoint')).hostname : req.headers.hostname; 50 | res.cookie('sdsession', token, { domain: domain, httpOnly: true }); 51 | res.status(201).json(session); 52 | }); 53 | }); 54 | } else { 55 | res.sendStatus(403); 56 | } 57 | }); 58 | }); 59 | 60 | router.delete('/current', function(req, res, next) { 61 | if (req.user) { 62 | var token = req.cookies['sdsession']; 63 | db.Session.findOne({where: {token: token}}) 64 | .then(session => { 65 | session.destroy(); 66 | }); 67 | var domain = (process.env.NODE_ENV == "production") ? new URL(config.get('endpoint')).hostname : req.headers.hostname; 68 | res.clearCookie('sdsession', { domain: domain }); 69 | res.sendStatus(204); 70 | } else { 71 | res.sendStatus(404); 72 | } 73 | }); 74 | 75 | module.exports = router; 76 | -------------------------------------------------------------------------------- /styles/alerts.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | 3 | #alerts { 4 | position: absolute; 5 | height: auto; 6 | width: auto; 7 | display: inline-block; 8 | bottom: 0px; 9 | right: 0px; 10 | left: 0px; 11 | margin: 10px; 12 | text-align: center; 13 | 14 | z-index: 1200; 15 | border-radius: 2px; 16 | 17 | .alert-success:first-child:after { 18 | border-bottom-color: #dff0d8; 19 | } 20 | 21 | .alert-info:first-child:after { 22 | border-bottom-color: #d9edf7; 23 | 24 | } 25 | 26 | .alert-warning:first-child:after { 27 | border-bottom-color: #fcf8e3; 28 | } 29 | 30 | 31 | .alert-danger:first-child:after { 32 | border-bottom-color: #eed3d7; 33 | } 34 | } 35 | 36 | .alert { 37 | padding: 10px 30px 10px 20px; 38 | border: none; 39 | display: inline-block; 40 | position: relative; 41 | white-space: normal; 42 | text-align: left; 43 | border-radius: 3px; 44 | font-weight: 600; 45 | // &:first-child { 46 | // border-top-left-radius: 3px; 47 | // border-top-right-radius: 3px; 48 | // } 49 | // &:last-child { 50 | // border-bottom-left-radius: 3px; 51 | // border-bottom-right-radius: 3px; 52 | // } 53 | 54 | .close { 55 | float: none; 56 | display: inline-block; 57 | font-size: inherit; 58 | line-height: inherit; 59 | position: absolute; 60 | right: 0px; 61 | top: 0px; 62 | width: 30px; 63 | height: 30px; 64 | line-height: 30px; 65 | text-align: center; 66 | } 67 | 68 | p { 69 | margin-top: 0px; 70 | margin-bottom: 15px; 71 | &:last-child { margin-bottom: 0px; } 72 | } 73 | 74 | > p + p { 75 | margin-top: 15px; 76 | } 77 | } 78 | 79 | .alert-success { 80 | color: white; 81 | background-color: #6bc36f; 82 | border-color: #d6e9c6; 83 | .alert-link { 84 | color: white; 85 | } 86 | } 87 | 88 | .alert-info { 89 | color: white; 90 | background-color: #4c9bd8; 91 | border-color: #bce8f1; 92 | .alert-link { 93 | color: white; 94 | } 95 | } 96 | 97 | .alert-warning { 98 | color: $dark; 99 | background-color: #ecc133; 100 | border-color: #fbeed5; 101 | .alert-link { 102 | color: $dark; 103 | } 104 | } 105 | 106 | .alert-danger { 107 | color: white; 108 | background-color: #c66554; 109 | border-color: #eed3d7; 110 | .alert-link { 111 | color: white; 112 | } 113 | } -------------------------------------------------------------------------------- /routes/api/space_messages.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var config = require('config'); 3 | const db = require('../../models/db'); 4 | const Sequelize = require('sequelize'); 5 | const Op = Sequelize.Op; 6 | const uuidv4 = require('uuid/v4'); 7 | 8 | var redis = require('../../helpers/redis'); 9 | var mailer = require('../../helpers/mailer'); 10 | var uploader = require('../../helpers/uploader'); 11 | var space_render = require('../../helpers/space-render'); 12 | var phantom = require('../../helpers/phantom'); 13 | 14 | var async = require('async'); 15 | var fs = require('fs'); 16 | var _ = require("underscore"); 17 | var archiver = require('archiver'); 18 | var request = require('request'); 19 | var url = require("url"); 20 | var path = require("path"); 21 | var crypto = require('crypto'); 22 | var glob = require('glob'); 23 | 24 | var express = require('express'); 25 | var router = express.Router({mergeParams: true}); 26 | 27 | // JSON MAPPINGS 28 | 29 | var userMapping = { 30 | _id: 1, 31 | nickname: 1, 32 | email: 1, 33 | avatar_thumb_uri: 1 34 | }; 35 | 36 | var spaceMapping = { 37 | _id: 1, 38 | name: 1, 39 | thumbnail_url: 1 40 | }; 41 | 42 | var roleMapping = { 43 | "none": 0, 44 | "viewer": 1, 45 | "editor": 2, 46 | "admin": 3 47 | } 48 | 49 | // MESSAGES 50 | 51 | router.get('/', function(req, res, next) { 52 | db.Message.findAll({where:{ 53 | space_id: req.space._id 54 | }, include: ['user']}) 55 | .then(function(messages) { 56 | res.status(200).json(messages); 57 | }); 58 | }); 59 | 60 | router.post('/', function(req, res, next) { 61 | var attrs = req.body; 62 | attrs.space_id = req.space._id; 63 | 64 | if (req.user) { 65 | attrs.user = req.user; 66 | attrs.user_id = req.user._id; 67 | } else { 68 | attrs.user = null; 69 | } 70 | 71 | var msg = attrs; 72 | msg._id = uuidv4(); 73 | 74 | db.Message.create(msg).then(function() { 75 | if (msg.message.length <= 1) return; 76 | // TODO reimplement notifications 77 | res.distributeCreate("Message", msg); 78 | }); 79 | }); 80 | 81 | router.delete('/:message_id', function(req, res, next) { 82 | db.Message.findOne({where:{ 83 | "_id": req.params.message_id 84 | }}).then(function(msg) { 85 | if (!msg) { 86 | res.sendStatus(404); 87 | } else { 88 | msg.destroy().then(function() { 89 | res.distributeDelete("Message", msg); 90 | }); 91 | } 92 | }); 93 | }); 94 | 95 | module.exports = router; 96 | -------------------------------------------------------------------------------- /styles/column.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | @import "mixins"; 3 | 4 | .column-row { 5 | min-height: 100%; 6 | box-sizing: content-box; 7 | 8 | display: -webkit-flex; 9 | display: flex; 10 | -webkit-flex-flow: row nowrap; 11 | flex-flow: row nowrap; 12 | -webkit-justify-content: center; 13 | justify-content:center; 14 | margin: auto; 15 | } 16 | 17 | .section { 18 | &.active { 19 | .column-border.in { 20 | background-color: rgba(40,140,215,0.25); 21 | border: 1px solid rgba(255,255,255,0.5); 22 | .edge-handle {display: block; } 23 | 24 | &:after{ 25 | border: 1px dotted rgba(40,140,215,0.5); 26 | } 27 | } 28 | } 29 | } 30 | 31 | .column { 32 | 33 | min-height: 50px; 34 | // min-height: 100%; 35 | position: relative; 36 | 37 | -webkit-flex: 1 auto; 38 | flex: 1 auto; 39 | 40 | // background-clip: content-box; 41 | // background-color: rgba(40,140,215,0.0125); 42 | // box-shadow: 0px 0px 0px 1px rgba(40,140,215,0.1); 43 | .artifact { 44 | &:last-child {margin-bottom: 0px; } 45 | 46 | &.over { 47 | @include transition( margin-top 0.1s ease-in-out 0.05s); 48 | position: relative; 49 | z-index: 1000; 50 | } 51 | } 52 | 53 | &:first-child .column-border .edge-handle:first-child { display: none !important;} 54 | &:last-child .column-border .edge-handle:last-child { display: none !important; } 55 | 56 | .column-border { 57 | border: 1px solid rgba(255,255,255,0.125); 58 | 59 | // &.in {display: block; } 60 | // border-style: solid; 61 | // border-width: 1px; 62 | // -moz-border-image: url('../images/border-dotted.png') 1 repeat; 63 | // -webkit-border-image: url('../images/border-dotted.png') 1 repeat; 64 | // -o-border-image: url('../images/border-dotted.png') 1 repeat; 65 | // border-image: url('../images/border-dotted.png') 1 fill repeat; 66 | 67 | position: absolute; 68 | width: auto; 69 | height: auto; 70 | right: -1px; 71 | left: -1px; 72 | top: -1px; 73 | bottom: -1px; 74 | 75 | z-index: 2000; 76 | pointer-events: none; 77 | .edge-handle {display: none; } 78 | &:after{ 79 | border: 1px dotted rgba(40,140,215,0.125); 80 | content: ""; 81 | display: block; 82 | 83 | position: absolute; 84 | width: auto; 85 | height: auto; 86 | right: -1px; 87 | left: -1px; 88 | top: -1px; 89 | bottom: -1px; 90 | 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /helpers/uploader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var config = require('config'); 5 | var s3 = null; 6 | 7 | // use AWS S3 or local folder depending on config 8 | if (config.get("storage_local_path")) { 9 | var AWS = require('mock-aws-s3'); 10 | AWS.config.basePath = config.get("storage_local_path"); 11 | s3 = new AWS.S3(); 12 | } else { 13 | var AWS = require('aws-sdk'); 14 | var storage_endpoint = config.get("storage_endpoint"); 15 | const ep = new AWS.Endpoint(storage_endpoint); 16 | 17 | AWS.config.update(new AWS.Config({ 18 | accessKeyId: process.env.MINIO_ACCESS_KEY, 19 | secretAccessKey: process.env.MINIO_SECRET_KEY, 20 | region: config.get("storage_region"), 21 | s3ForcePathStyle: true, 22 | signatureVersion: 'v4' 23 | })); 24 | s3 = new AWS.S3({ 25 | endpoint: ep 26 | }); 27 | } 28 | 29 | s3.createBucket({ 30 | Bucket: config.get("storage_bucket"), 31 | ACL: "public-read", 32 | GrantRead: "*" 33 | }, (err,res) => { 34 | console.log("createBucket",err,res); 35 | }); 36 | 37 | module.exports = { 38 | removeFile: (path, callback) => { 39 | const bucket = config.get("storage_bucket"); 40 | s3.deleteObject({ 41 | Bucket: bucket, Key: path 42 | }, (err, res) => { 43 | if (err){ 44 | console.error(err); 45 | callback(err); 46 | }else { 47 | callback(null, res); 48 | } 49 | }); 50 | }, 51 | uploadFile: function(fileName, mime, localFilePath, callback) { 52 | if (typeof(localFilePath)!="string") { 53 | callback({error:"missing path"}, null); 54 | return; 55 | } 56 | console.log("[storage] uploading", localFilePath, " to ", fileName); 57 | 58 | const bucket = config.get("storage_bucket"); 59 | const fileStream = fs.createReadStream(localFilePath); 60 | fileStream.on('error', function (err) { 61 | if (err) { 62 | console.error(err); 63 | callback(err); 64 | } 65 | }); 66 | fileStream.on('open', function () { 67 | s3.putObject({ 68 | Bucket: bucket, 69 | Key: fileName, 70 | ContentType: mime, 71 | Body: fileStream 72 | }, function (err) { 73 | if (err){ 74 | console.error(err); 75 | callback(err); 76 | } else { 77 | const url = config.get("storage_cdn") + "/" + fileName; 78 | console.log("[s3]" + localFilePath + " to " + url); 79 | callback(null, url); 80 | } 81 | }); 82 | }); 83 | } 84 | }; 85 | -------------------------------------------------------------------------------- /styles/search.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | @import "mixins"; 3 | 4 | #search-dialog { 5 | &:after { 6 | margin-left: -160px; 7 | } 8 | } 9 | 10 | .dialog-search { 11 | margin-left: 0; 12 | 13 | transform: translate3d(-22.5%, 0%, 100px) scale(1) !important; 14 | } 15 | 16 | .dialog-search-input { 17 | position: absolute; 18 | text-align: center; 19 | top: 0; 20 | left: 0; 21 | right: 112px; 22 | color: $medium; 23 | padding: 25px; 24 | background-color: rgba(245,245,245,0.95); 25 | border-top-left-radius: $radius*3; 26 | z-index: 100; 27 | } 28 | 29 | .dialog-search-results { 30 | padding: 40px !important; 31 | padding-top: 110px !important; 32 | margin-right: 92px; 33 | position: relative; 34 | @include clearfix(); 35 | font-size: 11px; 36 | line-height: 1.5; 37 | text-align: left !important; 38 | 39 | .search-result { 40 | max-width:100px; 41 | } 42 | 43 | > * { 44 | cursor: pointer; 45 | // &:hover {opacity: 0.8; } 46 | > * {pointer-events: none; } 47 | } 48 | .search-result-audio {} 49 | .search-result-video { 50 | &:nth-child(3n+1) {clear: both; } 51 | 52 | width: 33%; 53 | float: left; 54 | padding-right: 20px; 55 | padding-bottom: 20px; 56 | 57 | .thumbnail-wrapper { 58 | &:hover .thumbnail { opacity: 0.8; } 59 | span { 60 | display: block; 61 | top: 0; 62 | left: 0; 63 | position: absolute; 64 | &:before{ 65 | content: ""; 66 | display: block; 67 | padding-top: 100%; /* initial ratio of 1:1*/ 68 | padding-top: 55%; 69 | } 70 | background-color: $blue; 71 | color: $light; 72 | text-align: center; 73 | } 74 | } 75 | 76 | .thumbnail { 77 | width: 100%; 78 | margin-bottom: 10px; 79 | background-position: center; 80 | background-repeat: no-repeat; 81 | background-size: cover; 82 | border-radius: $radius; 83 | &:before{ 84 | content: ""; 85 | display: block; 86 | padding-top: 100%; /* initial ratio of 1:1*/ 87 | padding-top: 55%; 88 | } 89 | } 90 | } 91 | .search-result-image { 92 | display: inline-block; 93 | margin-right: 8px; 94 | margin-bottom: 8px; 95 | img { 96 | height: 100%; 97 | max-width: 120px; 98 | max-height: 120px; 99 | border-radius: $radius; 100 | } 101 | } 102 | } -------------------------------------------------------------------------------- /styles/table.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | @import "mixins"; 3 | 4 | .table-wrap { 5 | // margin-left: -40px; 6 | // margin-right: -40px; 7 | margin-bottom: 30px; 8 | } 9 | .table { 10 | width: 100%; 11 | font-family: $main-font; 12 | border-radius: $radius; 13 | border: 2px solid rgba(0,0,0,0.0125); 14 | } 15 | 16 | .table thead > tr > th:first-child, 17 | .table tbody > tr > th:first-child, 18 | .table tfoot > tr > th:first-child, 19 | .table thead > tr > td:first-child, 20 | .table tbody > tr > td:first-child, 21 | .table tfoot > tr > td:first-child { 22 | width:1%; 23 | padding-left: 25px; 24 | text-align: left !important; 25 | } 26 | 27 | .table thead > tr > th:last-child, 28 | .table tbody > tr > th:last-child, 29 | .table tfoot > tr > th:last-child, 30 | .table thead > tr > td:last-child, 31 | .table tbody > tr > td:last-child, 32 | .table tfoot > tr > td:last-child { 33 | padding-right: 25px; 34 | text-align: right; 35 | width: 1%; 36 | } 37 | 38 | 39 | .table thead > tr > td, 40 | .table tbody > tr > td, 41 | .table tfoot > tr > td { 42 | padding: 10px; 43 | border: none; 44 | font-size: 16px; 45 | line-height: inherit; 46 | //white-space: nowrap; 47 | vertical-align: middle; 48 | height: 60px; 49 | 50 | &.no-p {padding: 0px; } 51 | &.max {width: 100%; } 52 | &.min {width: 1%; } 53 | 54 | border-bottom: 2px solid rgba(0,0,0,0.0125) !important; 55 | } 56 | 57 | .table thead > tr > th, 58 | .table tbody > tr > th, 59 | .table tfoot > tr > th { 60 | border: none; 61 | line-height: inherit; 62 | //white-space: nowrap; 63 | vertical-align: middle; 64 | padding: 10px 20px; 65 | 66 | font-size: 10px; 67 | text-transform: uppercase; 68 | text-align: left; 69 | } 70 | 71 | .table tbody > tr:nth-child(odd) > td { 72 | background-color: rgba(0,0,0,0.025); 73 | } 74 | 75 | .table thead > tr:first-child > td:first-child, 76 | .table tbody > tr:first-child > td:first-child, 77 | .table tfoot > tr:first-child > td:first-child { 78 | border-top-left-radius: 3px; 79 | } 80 | .table thead > tr:first-child > td:last-child, 81 | .table tbody > tr:first-child > td:last-child, 82 | .table tfoot > tr:first-child > td:last-child { 83 | border-top-right-radius: 3px; 84 | } 85 | 86 | .table thead > tr:last-child > td:first-child, 87 | .table tbody > tr:last-child > td:first-child, 88 | .table tfoot > tr:last-child > td:first-child { 89 | border-bottom-left-radius: 3px; 90 | } 91 | 92 | .table thead > tr:last-child > td:last-child, 93 | .table tbody > tr:last-child > td:last-child, 94 | .table tfoot > tr:last-child > td:last-child { 95 | border-bottom-right-radius: 3px; 96 | } 97 | -------------------------------------------------------------------------------- /public/javascripts/spacedeck_modals.js: -------------------------------------------------------------------------------- 1 | 2 | var SpacedeckModals = { 3 | data: { 4 | active_modal: null, 5 | active_account_section: "user", 6 | active_space_profile_section: null, 7 | 8 | account_sections: [ 9 | { 10 | id: "user", 11 | title: "Profile", 12 | icon: "icon-user", 13 | }, 14 | { 15 | id: "language", 16 | title: "Language", 17 | icon: "icon-globe", 18 | }, 19 | { 20 | id: "email-notifications", 21 | title: "Notifications", 22 | icon: "icon-bell", 23 | }, 24 | { 25 | id: "reset-password", 26 | title: "Password", 27 | icon: "icon-lock-closed", 28 | }, 29 | { 30 | id: "remove-account", 31 | title: "Terminate", 32 | icon: "icon-logout", 33 | } 34 | ], 35 | folder_profile_sections: [ 36 | { 37 | id: "editors", 38 | title: "Editors", 39 | icon: "icon-user-group", 40 | count: 1 41 | }, 42 | { 43 | id: "visibility", 44 | title: "Visibility", 45 | icon: "icon-eye-open", 46 | count: 1 47 | } 48 | ], 49 | 50 | space_profile_sections: [ 51 | { 52 | id: "comments", 53 | title: "Comments", 54 | icon: "icon-messages", 55 | count: 1 56 | }, 57 | { 58 | id: "history", 59 | title: "History", 60 | icon: "icon-history", 61 | count: 1 62 | }, 63 | { 64 | id: "editors", 65 | title: "Editors", 66 | icon: "icon-user-group", 67 | count: 1 68 | }, 69 | { 70 | id: "visibility", 71 | title: "Visibility", 72 | icon: "icon-eye-open", 73 | count: 1 74 | } 75 | ] 76 | }, 77 | 78 | methods: { 79 | activate_modal: function(id) { 80 | this.active_modal = id; 81 | 82 | if (id == "folder-settings") { 83 | this.access_settings_space = this.active_folder; 84 | this.access_settings_memberships = this.active_space_memberships; 85 | this.editors_section = "list"; 86 | } 87 | }, 88 | 89 | close_modal: function() { 90 | this.active_modal = null; 91 | }, 92 | 93 | activate_account_section: function(section_id) { 94 | this.active_account_section = section_id; 95 | }, 96 | 97 | activate_space_profile_section: function(section_id) { 98 | this.active_space_profile_section = section_id; 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /styles/editors.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | @import "mixins"; 3 | 4 | #editors, #editors-list { 5 | @include user-select(none); 6 | 7 | h6 { 8 | padding: 15px 25px; 9 | margin: 0px; 10 | color: $medium; 11 | font-size: 10px; 12 | } 13 | 14 | ul { 15 | padding: 0px; 16 | margin: 0px; 17 | li { 18 | &:nth-child(1) .editor-avatar {background-color: #4a2f7e;} 19 | &:nth-child(2) .editor-avatar {background-color: #9b59b6;} 20 | &:nth-child(3) .editor-avatar {background-color: #3498db;} 21 | &:nth-child(4) .editor-avatar {background-color: #2ecc71;} 22 | &:nth-child(5) .editor-avatar {background-color: #f1c40f;} 23 | &:nth-child(6) .editor-avatar {background-color: #e67e22;} 24 | &:nth-child(7) .editor-avatar {background-color: #d55c4b;} 25 | &:nth-child(8) .editor-avatar {background-color: #6f4021;} 26 | &:nth-child(9) .editor-avatar {background-color: #ffffff;} 27 | &:nth-child(10) .editor-avatar {background-color: #95a5a6;} 28 | &:nth-child(11) .editor-avatar {background-color: #252525;} 29 | } 30 | } 31 | } 32 | 33 | 34 | #invite-message { 35 | height: 117px !important; 36 | } 37 | 38 | .editor > a, 39 | .editor > span { 40 | text-align: left; 41 | border-radius: $radius; 42 | display: block; 43 | // background-color: rgba(255,255,255,0.05); 44 | position: relative; 45 | // padding-left: 70px !important; 46 | min-height: 60px; 47 | border: none; 48 | 49 | 50 | .editor-avatar { 51 | margin-top: 7px; 52 | background-size: cover; 53 | background-position: center; 54 | margin-right: 15px; 55 | float: left; 56 | color: white !important; 57 | 58 | &.status-off, 59 | &.status-on { 60 | &:before { 61 | top: 0; 62 | right: 0; 63 | display: block; 64 | position: absolute; 65 | color: $darker ; 66 | border: 1px solid #292929; 67 | border-radius: 100px; 68 | 69 | padding: 4px; 70 | font-weight: 700; 71 | text-transform: uppercase; 72 | font-size: 0px; 73 | height: 4px; 74 | width: 4px; 75 | } 76 | } 77 | &.status-off:before {background-color: #d55c4b; content: "off"; display: none;} 78 | &.status-on:before {background-color: #2ecc71; content: "on"; } 79 | } 80 | 81 | .editor-email, 82 | .editor-name { 83 | font-family: $main-font; 84 | font-size: 13px; 85 | line-height: 1.4; 86 | display: block; 87 | font-weight: 300; 88 | overflow: hidden; 89 | text-overflow: ellipsis; 90 | white-space: nowrap; 91 | color: $medium; 92 | } 93 | .editor-email { 94 | opacity: 0.5; 95 | } 96 | } -------------------------------------------------------------------------------- /styles/type.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | @import "mixins"; 3 | 4 | @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;900&display=swap'); 5 | 6 | body { 7 | background-color: $light; 8 | color: $medium; 9 | font-weight: 300; 10 | font-weight: 400; 11 | 12 | font-family: $main-font, sans-serif; 13 | font-weight: 300; 14 | font-size: 15px; 15 | line-height: 1.6; 16 | } 17 | 18 | hr { 19 | margin: auto; 20 | // margin-top: ($line-height*2)-1; 21 | // margin-bottom: $line-height*2; 22 | width: auto; 23 | border: none; 24 | border-top: 2px solid rgba(0,0,0,0.025); 25 | } 26 | 27 | h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 { 28 | color: inherit; 29 | font-family: inherit; 30 | font-weight: 900; 31 | line-height: 1.3; 32 | margin-top: 0px; 33 | margin-bottom: 1em; 34 | // font-family: $main-font; 35 | display: block; 36 | &:last-child { margin-bottom: 0px; } 37 | } 38 | 39 | h1, .h1 { font-size: 54px; } 40 | h2, .h2 {font-size: 40px; } 41 | h3, .h3 {font-size: 30px; } 42 | h4, .h4 {font-size: 20px; } 43 | h5, .h5 {font-size: 16px; } 44 | h6, .h6 {font-size: 14px; } 45 | 46 | strong {font-weight: 500; } 47 | 48 | small {font-size: 75%; } 49 | 50 | a { 51 | color: black; 52 | } 53 | 54 | dl { 55 | background-color: rgba(0,0,0,0.05); 56 | color: rgba(136,136,136,1); 57 | border-radius: $radius; 58 | display: inline-block; 59 | margin-right: 5px; 60 | padding: 15px 25px; 61 | 62 | dt { 63 | font-weight: bold; 64 | } 65 | dd { 66 | padding: 0px; 67 | margin: 0px; 68 | } 69 | } 70 | 71 | ol, ul, p { 72 | font-size: 20px; 73 | line-height: 1.6; 74 | margin: 0px; 75 | margin-bottom: 1em; 76 | 77 | /*&:last-child { 78 | margin-bottom: 0px; 79 | }*/ 80 | 81 | small { 82 | font-size: 80%; 83 | line-height: 170%; 84 | display: inline-block; 85 | } 86 | 87 | a { 88 | -ms-word-break: break-all; 89 | word-break: break-all; 90 | /* Non standard for webkit */ 91 | word-break: break-word; 92 | 93 | -webkit-hyphens: auto; 94 | -moz-hyphens: auto; 95 | hyphens: auto; 96 | } 97 | 98 | b { 99 | font-weight: 700; 100 | } 101 | 102 | &.lead { 103 | font-weight: 200; 104 | font-size: 22px !important; 105 | line-height: 1.4; 106 | &.lead-lg { $font-size: 20px*1.5 !important; } 107 | &.lead-xl { font-size: 40px !important; } 108 | &.lead-xxl { font-size: 60px !important; } 109 | small { font-size: 60%; } 110 | b { font-weight: 600; } 111 | } 112 | } 113 | 114 | // ol, ul {padding-left: 20px; } 115 | // ol.lead {padding-left: 29px; } 116 | // ul.lead {padding-left: 23px; } 117 | -------------------------------------------------------------------------------- /public/javascripts/spacedeck_avatars.js: -------------------------------------------------------------------------------- 1 | 2 | var SpacedeckAvatars = { 3 | data: { 4 | uploading_avatar: false, 5 | uploading_folder_avatar: false, 6 | uploading_cover: false 7 | }, 8 | 9 | methods: { 10 | save_avatar_image: function(input, object_type, object) { 11 | if (input.files.length > 0) { 12 | var f = input.files[0]; 13 | 14 | var finished = function() { 15 | this.uploading_avatar = false; 16 | this.uploading_cover = false; 17 | this.uploading_folder_avatar = false; 18 | }.bind(this); 19 | 20 | if (!_.include(["image/jpeg","image/jpg","image/png","image/gif"], f.type)) { 21 | alert("Unsupported file type. Please upload JPEG, PNG or GIF."); 22 | finished(); 23 | return; 24 | } 25 | 26 | if (f.size > 1024*1024*3) { 27 | alert("File must be smaller than 3 megabytes."); 28 | finished(); 29 | return; 30 | } 31 | 32 | save_avatar_file(object_type, object, f, function(res) { 33 | finished(); 34 | this.uploading_avatar = false; 35 | this.uploading_cover = false; 36 | 37 | var newUri = res.avatar_thumb_uri; 38 | object.avatar_thumb_uri = newUri + "?cachebuster=" + Math.random(); 39 | }.bind(this), function(error) { 40 | alert("Upload failed: " + error); 41 | finished(); 42 | }); 43 | } 44 | }, 45 | 46 | save_space_avatar_image: function(viewmodel) { 47 | this.uploading_avatar = true; 48 | var func = this.save_avatar_image.bind(this); 49 | func(viewmodel.$event.target, "space", this.active_space); 50 | }, 51 | save_folder_avatar_image: function(viewmodel) { 52 | this.uploading_folder_avatar = true; 53 | var func = this.save_avatar_image.bind(this); 54 | func(viewmodel.$event.target, "space", this.active_folder); 55 | }, 56 | save_user_avatar_image: function(viewmodel) { 57 | this.uploading_avatar = true; 58 | var func = this.save_avatar_image.bind(this); 59 | func(viewmodel.$event.target, "user", viewmodel.$root.user); 60 | }, 61 | 62 | delete_user_avatar_image: function() { 63 | this.user.avatar_original_uri = ""; 64 | this.user.avatar_thumb_uri = ""; 65 | save_user(this.user,function(updated) { 66 | }.bind(this)); 67 | }, 68 | 69 | save_user_background_image: function(viewmodel) { 70 | var input = viewmodel.$event.target; 71 | 72 | this.uploading_cover = true; 73 | 74 | var f = input.files[0]; 75 | save_user_background_file(this.user, f, function(res) { 76 | this.user.background_original_uri = res.background_original_uri; 77 | this.uploading_cover = false; 78 | }.bind(this)); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /styles/chat.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | @import "mixins"; 3 | 4 | #chat-functions { 5 | position: absolute; 6 | bottom: 0px; 7 | right: 0px; 8 | .btn { 9 | display: block; 10 | margin-top: -10px; 11 | margin-left: -10px; 12 | } 13 | } 14 | #chat { 15 | #chat-message-new { 16 | position: absolute; 17 | bottom: 0px; 18 | width: 100%; 19 | display: table; 20 | min-height: 60px; 21 | label { 22 | vertical-align: middle; 23 | display: table-cell; 24 | font-size: 0px; 25 | textarea { 26 | display: inline-block; 27 | width: 100%; 28 | min-height: 90px; 29 | padding-right: 50px; 30 | font-size: 14px; 31 | line-height: 1.5 !important; 32 | font-weight: 300; 33 | } 34 | } 35 | } 36 | 37 | .overflow-y-scroll { 38 | bottom: 100px !important; 39 | } 40 | 41 | #chat-messages { 42 | width: 100%; 43 | padding: 0px; 44 | li { 45 | display: block; 46 | padding-bottom: 10px; 47 | padding-left: 25px; 48 | padding-right: 25px; 49 | padding-left: 60px; 50 | min-height: 60px; 51 | @include transition( all 0.2s ease-in-out); 52 | } 53 | } 54 | } 55 | 56 | 57 | #post-comment {} 58 | #new-comment {margin-bottom: 20px; } 59 | 60 | .comments { 61 | padding: 0; 62 | padding-top: 10px; 63 | margin-bottom: 0; 64 | //background-color: $light; 65 | font-size: 16px !important; 66 | font-family: $main-font !important; 67 | list-style: none; 68 | 69 | border-top-left-radius: $radius; 70 | border-top-right-radius: $radius; 71 | 72 | > li { 73 | padding-top: 10px; 74 | padding-bottom: 10px; 75 | padding-left: 40px; 76 | padding-right: 0px; 77 | &:last-child {border: none; } 78 | } 79 | 80 | .comment-author { 81 | pointer-events: none; 82 | position: relative; 83 | display: block; 84 | color: $darker; 85 | font-size: 11px; 86 | .btn-icon { 87 | position: absolute; 88 | left: -40px; 89 | top: -20px; 90 | } 91 | } 92 | .comment-body {} 93 | 94 | .comment-meta { 95 | display: block; 96 | list-style: none; 97 | padding: 0; 98 | margin: 0; 99 | 100 | font-size: 11px !important; 101 | 102 | li { 103 | margin-right: 10px; 104 | display: inline-block; 105 | 106 | &.pull-right { 107 | margin-right: 0px; 108 | margin-left: 10px; 109 | } 110 | 111 | > a { 112 | vertical-align: middle; 113 | display: inline-block; 114 | color: $lighter; 115 | } 116 | 117 | a { 118 | &:hover { 119 | color: $blue; 120 | } 121 | opacity: 0.5; 122 | } 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /styles/style.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | @import "../public/fonts/font"; 3 | @import "../public/fonts/unicode"; 4 | 5 | @import "normalize"; 6 | @import "mixins"; 7 | @import "helper"; 8 | @import "type"; 9 | 10 | @import "form"; 11 | @import "form-checkbox"; 12 | @import "form-file"; 13 | @import "form-input-group"; 14 | @import "form-range"; 15 | @import "form-select"; 16 | @import "table"; 17 | 18 | @import "icon"; 19 | @import "button"; 20 | @import "dropdown"; 21 | @import "dialog"; 22 | @import "overflow"; 23 | @import "alerts"; 24 | @import "close"; 25 | @import "modal"; 26 | @import "search"; 27 | 28 | @import "select-list"; 29 | @import "row"; 30 | 31 | @import "header"; 32 | @import "login"; 33 | @import "updates"; 34 | @import "profile"; 35 | @import "folder"; 36 | @import "editors"; 37 | @import "team"; 38 | 39 | @import "toolbar"; 40 | @import "color"; 41 | @import "typography"; 42 | @import "layout"; 43 | @import "filter"; 44 | @import "canvas"; 45 | @import "margin-columns"; 46 | @import "pattern"; 47 | @import "metrics"; 48 | 49 | @import "lasso"; 50 | @import "section"; 51 | @import "column"; 52 | @import "annotation"; 53 | 54 | @import "chat"; 55 | @import "space-sections"; 56 | 57 | @import "sidebar"; 58 | 59 | @import "artifact"; 60 | @import "handles"; 61 | 62 | @import "smoke"; 63 | @import "landing"; 64 | 65 | html, 66 | body { 67 | height:100%; 68 | background-color: white; 69 | color: $black; 70 | } 71 | 72 | body { 73 | max-width: 100%; 74 | padding: 0px; 75 | text-rendering: optimizeLegibility; 76 | cursor: default; 77 | } 78 | 79 | *[contenteditable="true"] { 80 | outline: none; 81 | } 82 | 83 | *, 84 | *:before, 85 | *:after { 86 | @include box-sizing(border-box); 87 | } 88 | 89 | .img img { 90 | max-width: 100%; 91 | height: auto; 92 | } 93 | 94 | /*.layer { 95 | @include transition( all 0.2s ease-in-out); 96 | @include backface-visibility(hidden); 97 | position: absolute; 98 | width: auto; 99 | height: auto; 100 | top: 0; 101 | left: 0; 102 | right: 0; 103 | bottom: 0; 104 | opacity: 0; 105 | pointer-events: none; 106 | // @include scale(0.95,0.95); 107 | display: none; 108 | z-index: 1000; 109 | &.top-layer { 110 | z-index: 3500; 111 | } 112 | &.in { 113 | display: block; 114 | &.top-layer { 115 | z-index: 3500; 116 | } 117 | z-index: 2000; 118 | // @include scale(1,1); 119 | pointer-events: auto; 120 | opacity: 1; 121 | } 122 | }*/ 123 | 124 | [draggable] { 125 | -moz-user-select: none; 126 | -khtml-user-select: none; 127 | -webkit-user-select: none; 128 | user-select: none; 129 | /* Required to make elements draggable in old WebKit */ 130 | -khtml-user-drag: element; 131 | -webkit-user-drag: element; 132 | } 133 | 134 | [v-cloak] { 135 | display: none !important; 136 | } 137 | -------------------------------------------------------------------------------- /views/partials/tool/filter.html: -------------------------------------------------------------------------------- 1 |

Filters

2 | 3 |
4 |
5 | 6 |
7 | 8 |
9 | {{active_style.brightness}} 10 | 11 |
12 |
13 |
14 | 15 |
16 | 17 |
18 | 19 |
20 | {{active_style.contrast}} 21 | 22 |
23 |
24 |
25 | 26 |
27 | 28 |
29 | 30 |
31 | {{active_style.saturation}} 32 | 33 |
34 |
35 |
36 | 37 |
38 | 39 |
40 | 41 |
42 | {{active_style.opacity}} 43 | 44 |
45 |
46 |
47 | 48 |
49 | 50 |
51 | 52 |
53 | {{active_style.hue}} 54 | 55 |
56 |
57 |
58 | 59 |
60 | 61 |
62 | 63 |
64 | {{active_style.blur}} 65 | 66 |
67 |
68 |
69 |
70 | 71 |
72 | 73 |
74 | -------------------------------------------------------------------------------- /styles/main.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | @import "mixins"; 3 | 4 | .wrapper { 5 | position: relative; 6 | margin: auto; 7 | max-width: 1160px; 8 | min-height: 100%; 9 | height: 100%; 10 | } 11 | 12 | .main { 13 | background-color: $light; 14 | color: $medium; 15 | height: auto; 16 | top: 80px !important; 17 | bottom: 0; 18 | position: absolute; 19 | 20 | > .overflow-y-scroll { 21 | background-color: $light; 22 | height: 100%; 23 | } 24 | 25 | .header { 26 | @include clearfix(); 27 | position: relative; 28 | z-index: 500; 29 | min-height: 60px; 30 | margin: 40px; 31 | 32 | .header-main { 33 | height: 60px; 34 | @include clearfix(); 35 | } 36 | 37 | .header-center { 38 | text-align: center; 39 | pointer-events: none; 40 | z-index: 5000; 41 | position: absolute; 42 | width: 100%; 43 | top: 0; 44 | * {pointer-events: auto; } 45 | ul { 46 | margin: auto; 47 | display: block; 48 | vertical-align: middle; 49 | list-style: none; 50 | padding-left: 0px; 51 | } 52 | .profile-avatar { 53 | margin: auto; 54 | display: block; 55 | margin-top: -80px; 56 | img { 57 | border-radius: 100%; 58 | } 59 | } 60 | } 61 | 62 | .header-center > ul li span { padding-left: 5px; padding-right: 5px;} 63 | .header-left > ul li span { padding-right: 10px;} 64 | .header-right > ul li span { padding-left: 10px;} 65 | 66 | .header-center, 67 | .header-right, 68 | .header-left { 69 | height: 60px; 70 | line-height: 60px; 71 | } 72 | 73 | .header-right { 74 | float: right; 75 | @include transition( all 0.25s ease-in-out); 76 | @include translateX (0px); 77 | } 78 | 79 | .header-left { 80 | float: left; 81 | 82 | > h4 { 83 | vertical-align: middle; 84 | display: inline-block; 85 | } 86 | } 87 | } 88 | 89 | .content { 90 | margin: 40px; 91 | //@include transition( all 0.25s ease-in-out); 92 | .section { 93 | border-bottom: 2px solid rgba(0,0,0,0.04); 94 | margin-bottom: 80px; 95 | padding-bottom: 80px; 96 | @include clearfix(); 97 | &:last-child { 98 | border-bottom: none; 99 | } 100 | 101 | form { 102 | // border-radius: $radius; 103 | // background-color: white; 104 | // display: block; 105 | // padding: 40px; 106 | } 107 | } 108 | .img { 109 | img { 110 | border-radius: $radius; 111 | } 112 | margin-right: 20px; 113 | position: relative; 114 | .btn { 115 | position: absolute; 116 | top: 0px; 117 | right: 0px; 118 | } 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /styles/player.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | @import "mixins"; 3 | 4 | 5 | #player { 6 | 7 | -webkit-overflow-scrolling: touch; 8 | position: absolute; 9 | border-radius: $radius; 10 | 11 | top: 100px; 12 | left: 100px; 13 | bottom: 100px; 14 | right: 100px; 15 | width: auto; 16 | height: auto; 17 | z-index: 0; 18 | background-color: black; 19 | white-space: nowrap; 20 | z-index: 10000; 21 | display: none; 22 | 23 | #player-controls-1{ 24 | height: 100px; 25 | 26 | &:hover #player-controls-2{ 27 | @include scale(1,1); 28 | } 29 | 30 | #player-controls-2{ 31 | @include transition( all 0.4s ease-in-out); 32 | } 33 | 34 | position: absolute; 35 | bottom: 0; 36 | left: 0; 37 | right: 0; 38 | opacity: 0; 39 | @include transition( all 0.4s ease-in-out); 40 | @include translateY (20%); 41 | &:hover { 42 | @include translateY (0); 43 | opacity: 1; 44 | #player-controls { 45 | @include rotateX(0deg); 46 | } 47 | } 48 | 49 | @include backface-visibility(hidden); 50 | -webkit-perspective: 1000; 51 | -moz-perspective: 1000; 52 | -ms-perspective: 1000; 53 | perspective: 1000; 54 | 55 | @include perspective-origin(bottom center); 56 | 57 | #player-controls { 58 | @include rotateX(-90deg); 59 | @include transform-origin(bottom center); 60 | @include backface-visibility(hidden); 61 | @include transition( all 0.4s ease-in-out); 62 | 63 | background-color: rgba(0,0,0,0.25); 64 | background-color: $darker; 65 | border-radius: $radius; 66 | margin: 20px; 67 | 68 | /* As of August 2012, only supported in Chrome 21+ */ 69 | display: -webkit-flex; 70 | flex-direction: row; 71 | align-items: center; 72 | 73 | .time { 74 | font-size: 20px; 75 | line-height: 60px; 76 | display: block; 77 | padding: 0 20px; 78 | &.current {border-left: 2px solid rgba(0,0,0,0.1); } 79 | &.total {border-right: 2px solid rgba(0,0,0,0.1); } 80 | } 81 | 82 | .progress-bar { 83 | flex: 1; 84 | position: relative; 85 | } 86 | 87 | .progress-bar { 88 | background-color: rgba(0,0,0,0.25); 89 | width: 100%; 90 | height: 60px; 91 | position: relative; 92 | 93 | .progress-bar-buffer, 94 | .progress-bar-progress, 95 | .progress-bar-handle { 96 | position: absolute; 97 | height: 60px; 98 | } 99 | .progress-bar-handle { 100 | width: 4px; 101 | background-color: $light; 102 | z-index: 10; 103 | margin-left: -2px; 104 | height: 60px; 105 | border-radius: 30px; 106 | } 107 | .progress-bar-buffer { background-color: rgba(0,0,0,0.25); } 108 | .progress-bar-progress { background-color: $blue; } 109 | .progress-bar-handle {} 110 | } 111 | } 112 | } 113 | } -------------------------------------------------------------------------------- /views/partials/meta.html: -------------------------------------------------------------------------------- 1 | 72 | -------------------------------------------------------------------------------- /styles/select-list.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | @import "mixins"; 3 | 4 | 5 | .white .select-list { 6 | li { 7 | &.checked { 8 | 9 | > a, 10 | > span { 11 | } 12 | } 13 | 14 | &:hover { 15 | > a, 16 | > span { 17 | background-color: rgba(0,0,0,0.025) !important; 18 | } 19 | } 20 | 21 | > a, 22 | > span { 23 | color: $medium; 24 | } 25 | } 26 | } 27 | 28 | .select-list { 29 | &:empty:before { 30 | position: absolute; 31 | top: 50%; 32 | left: 50%; 33 | margin: -50%; 34 | width: 100%; 35 | display: block; 36 | font-size: 13px; 37 | line-height: 1px; 38 | margin-top: -6px; 39 | content:attr(data-placeholder); 40 | color: $medium; 41 | opacity: 0.5; 42 | } 43 | 44 | background-clip: padding-box; 45 | //font-size: 15px; 46 | //line-height: 14px; 47 | list-style: none; 48 | margin: 0px; 49 | padding: 15px 0; 50 | text-align: left; 51 | // background-color: $dark; 52 | border-radius: $radius; 53 | 54 | .divider + li span {border: none !important; } 55 | 56 | .divider { 57 | @include backface-visibility(hidden); 58 | 59 | margin: 10px 0; 60 | border: 1px solid rgba(0,0,0,0.05); 61 | height: 0; 62 | } 63 | 64 | // li.divider {border-bottom: 2px solid rgba(0,0,0,0.025); } 65 | 66 | li { 67 | width: 100%; 68 | display: block; 69 | position: relative; 70 | margin: -2px 0 ; 71 | 72 | &.checked { 73 | opacity: 1; 74 | 75 | &:before { 76 | background-color: $dark !important; 77 | display: block; 78 | } 79 | > a, 80 | > span { 81 | color: $dark; 82 | } 83 | } 84 | 85 | &:hover { 86 | background-color: black; 87 | 88 | > a, 89 | > span { 90 | color: white; 91 | } 92 | } 93 | 94 | &.select-list-title:before { 95 | display: none !important; 96 | } 97 | 98 | &:before { 99 | top: 50%; 100 | left: 10px; 101 | margin-top: -3px; 102 | content: ""; 103 | display: block; 104 | position: absolute; 105 | height: 6px; 106 | width: 6px; 107 | background-color: $light; 108 | border-radius: 100%; 109 | display: none; 110 | } 111 | 112 | > b, 113 | > a, 114 | > span { 115 | display: block; 116 | cursor: pointer; 117 | white-space: nowrap; 118 | margin: 0 25px; 119 | padding: 10px 0px; 120 | // line-height: 50px; 121 | overflow: hidden; 122 | text-overflow: ellipsis; 123 | max-width: 100%; 124 | border-top: 2px solid rgba(0,0,0,0.025) !important; 125 | .icon { 126 | &.icon-sm { 127 | margin: -7px; 128 | margin-right: 14px; 129 | } 130 | &.icon-md { 131 | margin: -13px; 132 | margin-right: 7px; 133 | } 134 | } 135 | } 136 | 137 | &:first-child { 138 | > b, 139 | > a, 140 | > span { 141 | border: none !important; 142 | } 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /views/partials/tool/layout.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 7 | 10 | 13 | 16 | 19 | 22 |
23 |
24 | 27 | 30 | 33 | 36 | 39 | 42 | 45 |
46 | 47 | 63 | 64 |
65 |
66 | -------------------------------------------------------------------------------- /views/partials/tool/stroke.html: -------------------------------------------------------------------------------- 1 |

Stroke

2 | 3 |
4 |
5 |
6 | 7 | 10 | px 11 |
12 |
13 | 14 |
15 |
16 | 17 | 18 | 21 | px 22 |
23 |
24 | 25 |
26 |
27 | 28 |
29 | 32 | 35 | 38 |
39 |
40 |
41 | 42 | 46 |
47 | 48 |
49 | 50 |
51 | 52 | 68 | 69 | -------------------------------------------------------------------------------- /views/partials/tool/pattern.html: -------------------------------------------------------------------------------- 1 |

Pattern

2 | 3 |
4 |
5 | 9 | 20 |
21 | 22 |
23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |
36 |
37 | 38 |
39 |
40 | 41 |
42 | 50 43 | 44 |
45 |
46 |
47 | 48 |
-------------------------------------------------------------------------------- /views/partials/tool/toolbar-text.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 15 | 16 | 17 | 18 | 29 | 30 | 31 | 32 | 42 | 43 | 53 |
54 | 55 |
56 | 60 | 64 |
65 | 66 |
67 | 71 |
72 |
73 | -------------------------------------------------------------------------------- /middlewares/space_helpers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const db = require('../models/db'); 4 | var config = require('config'); 5 | 6 | module.exports = (req, res, next) => { 7 | let spaceId = req.params.id; 8 | 9 | let finalizeReq = (space, role) => { 10 | if (role === "none") { 11 | res.status(403).json({ 12 | "error": "access denied" 13 | }); 14 | } else { 15 | req['space'] = space; 16 | req['spaceRole'] = role; 17 | res.header("x-spacedeck-space-role", req['spaceRole']); 18 | next(); 19 | } 20 | }; 21 | 22 | var finalizeAnonymousLogin = function(space, spaceAuth) { 23 | var role = "none"; 24 | 25 | if (spaceAuth && (spaceAuth === space.edit_hash)) { 26 | role = "editor"; 27 | } else { 28 | if (space.access_mode == "public") { 29 | role = "viewer"; 30 | } else { 31 | role = "none"; 32 | } 33 | } 34 | 35 | if (req.user) { 36 | db.getUserRoleInSpace(space, req.user, function(newRole) { 37 | if (newRole == "admin" && (role == "editor" || role == "viewer")) { 38 | finalizeReq(space, newRole); 39 | } else if (newRole == "editor" && (role == "viewer")) { 40 | finalizeReq(space, newRole); 41 | } else { 42 | finalizeReq(space, role); 43 | } 44 | }); 45 | } else { 46 | finalizeReq(space, role); 47 | } 48 | }; 49 | 50 | var userMapping = { 51 | '_id': 1, 52 | 'nickname': 1, 53 | 'email': 1 54 | }; 55 | 56 | db.Space.findOne({where: { 57 | "_id": spaceId 58 | }}).then(function(space) { 59 | 60 | if (space) { 61 | if (space.access_mode == "public") { 62 | if (space.password) { 63 | if (req.spacePassword) { 64 | if (req.spacePassword === space.password) { 65 | finalizeAnonymousLogin(space, req["spaceAuth"]); 66 | } else { 67 | res.status(403).json({ 68 | "error": "password_wrong" 69 | }); 70 | } 71 | } else { 72 | res.status(401).json({ 73 | "error": "password_required" 74 | }); 75 | } 76 | } else { 77 | finalizeAnonymousLogin(space, req["spaceAuth"]); 78 | } 79 | 80 | } else { 81 | // space is private 82 | 83 | // special permission for screenshot/pdf export from backend 84 | if (req.query['api_token'] && req.query['api_token'] == config.get('phantom_api_secret')) { 85 | finalizeReq(space, "viewer"); 86 | return; 87 | } 88 | 89 | if (req.user) { 90 | db.getUserRoleInSpace(space, req.user, function(role) { 91 | if (role == "none") { 92 | finalizeAnonymousLogin(space, req["spaceAuth"]); 93 | } else { 94 | finalizeReq(space, role); 95 | } 96 | }); 97 | } else { 98 | if (req.spaceAuth && space.edit_hash) { 99 | finalizeAnonymousLogin(space, req["spaceAuth"]); 100 | } else { 101 | res.status(403).json({ 102 | "error": "auth_required" 103 | }); 104 | } 105 | } 106 | } 107 | } else { 108 | res.status(404).json({ 109 | "error": "space_not_found" 110 | }); 111 | } 112 | }); 113 | } 114 | -------------------------------------------------------------------------------- /styles/profile.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | @import "mixins"; 3 | #short-profile { 4 | position: absolute; 5 | top: 50%; 6 | left: 50%; 7 | width: 300px; 8 | height: 200px; 9 | border-radius: $radius; 10 | background-color: white; 11 | box-shadow: 0 1px 10px rgba(0,0,0,0.05); 12 | z-index: 5000; 13 | 14 | #short-profile-header { 15 | height: 100px; 16 | width: 300px; 17 | border-top-left-radius: $radius; 18 | border-top-right-radius: $radius; 19 | position: relative; 20 | background-size: cover; 21 | background-position: center; 22 | background-color: rgba(0,0,0,0.1); 23 | } 24 | 25 | #short-profile-image { 26 | position: absolute; 27 | bottom: -30px; 28 | left: 50%; 29 | margin-left: -40px; 30 | } 31 | #short-profile-actions { 32 | position: absolute; 33 | top: 0; 34 | right: 0; 35 | margin: 10px; 36 | } 37 | #short-profile-details { 38 | padding-top: 40px; 39 | .nickname, 40 | .bio { 41 | text-align: center; 42 | display: block; 43 | } 44 | .bio { color: $medium;} 45 | .nickname {color: $darker; font-size: 18px;} 46 | } 47 | } 48 | 49 | 50 | 51 | #profile-details { 52 | padding: 30px 40px; 53 | background-color: white; 54 | @include clearfix(); 55 | text-align: center; 56 | position: relative; 57 | .profile-head { 58 | margin-bottom: 20px; 59 | } 60 | .profile-image { 61 | position: relative; 62 | margin: auto; 63 | display: inline-block; 64 | margin-bottom: 20px; 65 | line-height: 200px; 66 | width: 200px; 67 | height: 200px; 68 | border: 4px solid white; 69 | border-radius: 8px; 70 | box-shadow: 0 1px 10px rgba(0,0,0,0.05); 71 | } 72 | 73 | .profile-cover { 74 | position: absolute; 75 | height: 200px; 76 | width: 100%; 77 | top: 0; 78 | left: 0; 79 | background-size: cover; 80 | background-position: center; 81 | z-index: 0; 82 | } 83 | .profile-username { 84 | margin: 0; 85 | font-size: 20px; 86 | color: $darker; 87 | } 88 | .profile-description { 89 | margin: 0; 90 | font-size: 20px; 91 | } 92 | 93 | ul { 94 | list-style: none; 95 | margin: 0; 96 | padding: 0; 97 | // text-align: center; 98 | margin-top: -3px; 99 | 100 | li { 101 | vertical-align: middle; 102 | display: inline-block; 103 | // margin: 0px 7px; 104 | margin-right: 3px; 105 | color: $medium; 106 | text-transform: uppercase; 107 | font-family: $main-font; 108 | font-size: 11px; 109 | opacity: 0.5; 110 | &:hover {opacity: 1; } 111 | > .icon {margin-right: 3px; } 112 | a { 113 | color: $medium; 114 | &:hover {color: $blue; } 115 | } 116 | } 117 | } 118 | } 119 | 120 | 121 | #account { 122 | padding-top: 64px; 123 | #cover-image { 124 | position: absolute; 125 | height: 100%; 126 | width: 100%; 127 | top: 0; 128 | left: 0; 129 | opacity: 0.2; 130 | background-size: cover; 131 | background-position: center; 132 | } 133 | #profile-image { 134 | position: relative; 135 | 136 | img { 137 | border-radius: $radius; 138 | overflow: hidden; 139 | } 140 | #remove-profile-image { 141 | position: absolute; 142 | top: 0; 143 | left: 0; 144 | margin: -16px; 145 | } 146 | } 147 | } 148 | 149 | -------------------------------------------------------------------------------- /styles/form-checkbox.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | @import "mixins"; 3 | @import "unicode"; 4 | 5 | .checkbox.checked:before { opacity: 1; @extend .icon-input-checkbox-checked:before;} 6 | .radio.checked:before { opacity: 1; @extend .icon-input-radio-checked:before;} 7 | 8 | .checkbox:hover:before { @extend .icon-input-checkbox-checked:before;} 9 | .radio:hover:before { @extend .icon-input-radio-checked:before;} 10 | 11 | .checkbox:active:before { opacity: 1; @extend .icon-input-checkbox-checked:before; } 12 | .radio:active:before { opacity: 1; @extend .icon-input-radio-checked:before; } 13 | 14 | .checkbox:hover:before { @extend .icon-input-checkbox-checked:before; } 15 | .radio:hover:before { @extend .icon-input-radio-checked:before; } 16 | 17 | .checkbox:active { color: white; } 18 | .radio:active { color: white; } 19 | 20 | @media screen and (-webkit-min-device-pixel-ratio:0) {} 21 | 22 | .radio, 23 | .checkbox { 24 | @include user-select(none); 25 | display: inline-block !important; 26 | padding: 20px 10px; 27 | padding-left: 35px; 28 | line-height: 1.5; 29 | width: 100%; 30 | text-align: left; 31 | font-weight: normal; 32 | cursor: pointer; 33 | border-radius: $radius; 34 | 35 | vertical-align: middle; 36 | position: relative; 37 | 38 | input { 39 | padding: 0; 40 | margin:0; 41 | vertical-align: bottom; 42 | *overflow: hidden; 43 | cursor: pointer; 44 | position: absolute; 45 | left: 14px; 46 | top: 50%; 47 | margin-top: -5px; 48 | width: 14px; 49 | height: 14px; 50 | display: none; 51 | } 52 | 53 | &:before { 54 | opacity: 0.25; 55 | @include icon; 56 | @extend .icon-input-radio:before; 57 | margin-left: -18px; 58 | // margin-top: -8px; 59 | line-height: 60px; 60 | position: absolute; 61 | left: 0; 62 | top: 0; 63 | } 64 | 65 | &.plain { 66 | background-color: transparent; 67 | margin: 0; 68 | margin-top: -6px; 69 | padding: 0px; 70 | line-height: 44px; 71 | padding-left: 33px; 72 | width: 100%; 73 | 74 | input { 75 | margin-top: -7px; 76 | left: 6px; 77 | } 78 | 79 | &:after { 80 | background-color: #121212; 81 | -webkit-box-shadow: 0 0 0 4px #121212; 82 | box-shadow: 0 0 0 4px #121212; 83 | } 84 | } 85 | 86 | &.only { 87 | margin: 0; 88 | width: 100%; 89 | text-align: left; 90 | line-height: 60px !important; 91 | vertical-align: middle; 92 | position: relative; 93 | background-color: transparent; 94 | padding: 0px !important; 95 | font-size: 0px; 96 | width: 60px; 97 | height: 60px; 98 | 99 | input { 100 | float: none; 101 | margin: 0px; 102 | position: absolute; 103 | top: 50%; 104 | left: 50%; 105 | margin: -7px; 106 | } 107 | 108 | &:before { 109 | margin-top: 0px; 110 | margin-left: 0px; 111 | } 112 | } 113 | 114 | &.input-lg { 115 | padding: 22px; 116 | padding-left: 50px; 117 | font-size: 16px; 118 | height: 65px; 119 | line-height: 20px; 120 | height: 65px; 121 | font-weight: 300; 122 | 123 | input { 124 | margin-top: -8px; 125 | left: 20px; 126 | } 127 | 128 | &:after { 129 | margin-top: -12px; 130 | margin-left: 16px; 131 | } 132 | } 133 | } 134 | 135 | 136 | 137 | .checkbox-group > * { 138 | display: inline-block !important; 139 | width: auto !important; 140 | padding-right: 15px !important; 141 | } 142 | -------------------------------------------------------------------------------- /views/partials/tool/search.html: -------------------------------------------------------------------------------- 1 | 71 | -------------------------------------------------------------------------------- /public/javascripts/spacedeck_account.js: -------------------------------------------------------------------------------- 1 | /* 2 | SpacedeckAccount 3 | This module contains functions dealing with the spacedeck account. 4 | */ 5 | 6 | SpacedeckAccount = { 7 | data: { 8 | account_confirmed_sent: false, 9 | account_tab: 'invoices', 10 | password_change_error: null, 11 | feedback_text: "", 12 | }, 13 | methods: { 14 | show_account: function() { 15 | this.activate_dropdown('account'); 16 | }, 17 | 18 | account_save_user_digest: function(val) { 19 | this.user.prefs_email_digest = val; 20 | this.save_user(function() { 21 | }); 22 | }, 23 | 24 | account_save_user_notifications: function(val) { 25 | this.user.prefs_email_notifications = val; 26 | this.save_user(function() { 27 | }); 28 | }, 29 | 30 | save_user_email: function() { 31 | this.save_user(function() { 32 | }.bind(this)); 33 | }, 34 | 35 | save_user_language: function(lang) { 36 | localStorage.lang = lang; 37 | this.user.prefs_language = lang; 38 | this.save_user(function() { 39 | window._spacedeck_location_change = true; 40 | location.href="/spaces"; 41 | }.bind(this)); 42 | }, 43 | 44 | save_user: function(on_success) { 45 | if (this.user.email_changed) { 46 | this.user.confirmed_at = null; 47 | } 48 | window._spacedeck_location_change = true; 49 | 50 | save_user(this.user, function(user) { 51 | if (on_success) on_success(); 52 | else location.href="/spaces"; 53 | 54 | }.bind(this), function(xhr){ 55 | console.error(xhr) 56 | }); 57 | }, 58 | 59 | save_user_password: function(oldPass, newPass, newPassConfirm) { 60 | this.password_change_error = null; 61 | 62 | if (!oldPass) { 63 | this.password_change_error = "Current password required"; 64 | return; 65 | } 66 | 67 | if (!newPass || !newPassConfirm) { 68 | this.password_change_error = "New password/password confirmation required"; 69 | return; 70 | } 71 | 72 | if (newPass!=newPassConfirm) { 73 | this.password_change_error = "New Passwords do not match"; 74 | return; 75 | } 76 | 77 | if (newPass.length < 6) { 78 | this.password_change_error = "New Password to short"; 79 | return; 80 | } 81 | 82 | save_user_password(this.user, oldPass, newPass, function() { 83 | alert("OK. Password Changed."); 84 | this.password_change_current = ""; 85 | this.password_change_new = ""; 86 | this.password_change_new_confirmation = ""; 87 | }.bind(this), function(xhr) { 88 | if (xhr.status == 403) { 89 | this.password_change_error = "Old Password not correct"; 90 | } else { 91 | this.password_change_error = "Something went wrong. Please try again later."; 92 | } 93 | }.bind(this)); 94 | }, 95 | 96 | confirm_again: function() { 97 | resent_confirm_mail(this.user, function(re) { 98 | this.account_confirmed_sent = true; 99 | 100 | alert(__("confirm_again")); 101 | 102 | }.bind(this), function(xhr){ 103 | console.error(xhr); 104 | alert("Something went wrong, please try again."); 105 | }); 106 | }, 107 | 108 | confirm_account: function(token) { 109 | confirm_user(this.user, token, function(re) { 110 | smoke.alert(__("confirmed"), function() { 111 | this.redirect_to("/spaces"); 112 | }.bind(this)); 113 | }.bind(this), function(xhr) { 114 | console.error(xhr); 115 | alert(xhr.responseText); 116 | this.redirect_to("/spaces"); 117 | }.bind(this)); 118 | }, 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /views/partials/team.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |

Spacedeck Team Management

6 | 7 |
8 | You are not in a team yet. Please upgrade first. 9 |
10 | 11 |
12 |
13 |

[[__("team_name")]]

14 |
15 | 16 | 17 | 18 | [[__("save")]] 19 | 20 | 21 |
22 |
23 | 24 |
25 |

[[__("subdomain")]]

26 |
27 | 28 | 29 | 30 | [[__("save")]] 31 | 32 | 33 |
34 |
35 | 36 |
37 |

Members

38 | 39 |

40 | New members will get an invitation email. After the invitation was used, the member is active. The number of active members in your team will affect your monthly charge. 41 |

42 | 43 |
44 | 45 | 46 | 47 | [[__("add")]] 48 | ✓ [[__("invited")]] 49 | 50 | 51 |
52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 70 | 71 | 74 | 75 | 79 | 80 | 85 | 86 | 87 | 88 |
[[__("email")]] [[__("name")]] [[__("role")]]
72 | {{u.nickname}} 73 | 76 | [[__("role_admin")]] 77 | [[__("role_member")]] 78 | 81 | [[__("remove")]] 82 | [[__("promote")]] 83 | [[__("demote")]] 84 |
89 |
90 |
91 |
92 | -------------------------------------------------------------------------------- /routes/root.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const config = require('config'); 4 | 5 | const redis = require('../helpers/redis'); 6 | const express = require('express'); 7 | const crypto = require('crypto'); 8 | const router = express.Router(); 9 | const mailer = require('../helpers/mailer'); 10 | const _ = require('underscore'); 11 | 12 | const db = require('../models/db'); 13 | const Sequelize = require('sequelize'); 14 | const Op = Sequelize.Op; 15 | const uuidv4 = require('uuid/v4'); 16 | 17 | router.get('/', (req, res) => { 18 | res.render('index', { title: 'Spaces' }); 19 | }); 20 | 21 | router.get('/ping', (req, res) => { 22 | res.status(200).json({"status": "ok"}) 23 | }); 24 | 25 | router.get('/spaces', (req, res) => { 26 | res.render('spacedeck', { title: 'Spaces' }); 27 | }); 28 | 29 | router.get('/not_found', (req, res) => { 30 | res.render('not_found', { title: 'Spaces' }); 31 | }); 32 | 33 | router.get('/confirm/:token', (req, res) => { 34 | res.render('spacedeck', { title: 'Space' }); 35 | }); 36 | 37 | router.get('/folders/:id', (req, res) => { 38 | res.render('spacedeck', {}); 39 | }); 40 | 41 | router.get('/signup', (req, res) => { 42 | res.render('spacedeck', {}); 43 | }); 44 | 45 | router.get('/accept/:id', (req, res) => { 46 | res.render('spacedeck', {}); 47 | }); 48 | 49 | router.get('/password-reset', (req, res) => { 50 | res.render('spacedeck', { title: 'Signup' }); 51 | }); 52 | 53 | router.get('/password-confirm/:token', (req, res) => { 54 | res.render('spacedeck', { title: 'Signup' }); 55 | }); 56 | 57 | router.get('/de/*', (req, res) => { 58 | res.redirect("/t/de"); 59 | }); 60 | 61 | router.get('/de', (req, res) => { 62 | res.redirect("/t/de"); 63 | }); 64 | 65 | router.get('/fr/*', (req, res) => { 66 | res.redirect("/t/fr"); 67 | }); 68 | 69 | router.get('/fr', (req, res) => { 70 | res.redirect("/t/fr"); 71 | }); 72 | 73 | router.get('/en/*', (req, res) => { 74 | res.redirect("/t/en"); 75 | }); 76 | 77 | router.get('/en', (req, res) => { 78 | res.redirect("/t/end"); 79 | }); 80 | 81 | router.get('/account', (req, res) => { 82 | res.render('spacedeck'); 83 | }); 84 | 85 | router.get('/login', (req, res) => { 86 | res.render('spacedeck'); 87 | }); 88 | 89 | router.get('/logout', (req, res) => { 90 | res.render('spacedeck'); 91 | }); 92 | 93 | router.get('/contact', (req, res) => { 94 | res.render('public/contact'); 95 | }); 96 | 97 | router.get('/about', (req, res) => { 98 | res.render('public/about'); 99 | }); 100 | 101 | router.get('/terms', (req, res) => { 102 | res.render('public/terms'); 103 | }); 104 | 105 | router.get('/privacy', (req, res) => { 106 | res.render('public/privacy'); 107 | }); 108 | 109 | router.get('/t/:id', (req, res) => { 110 | res.cookie('spacedeck_locale', req.params.id, { maxAge: 900000, httpOnly: true }); 111 | var path = "/"; 112 | if (req.query.r=="login" || req.query.r=="signup") { 113 | path = "/"+req.query.r; 114 | } 115 | res.redirect(path); 116 | }); 117 | 118 | router.get('/s/:token', (req, res) => { 119 | var token = req.params.token; 120 | if (token.split("-").length > 0) { 121 | token = token.split("-")[0]; 122 | } 123 | 124 | db.Space.findOne({where: {"edit_hash": token}}).then(function (space) { 125 | if (space) { 126 | if (req.accepts('text/html')){ 127 | res.redirect("/spaces/"+space._id + "?spaceAuth=" + token); 128 | } else { 129 | res.status(200).json(space); 130 | } 131 | } else { 132 | if (req.accepts('text/html')) { 133 | res.status(404).render('not_found', { title: 'Page Not Found.' }); 134 | } else { 135 | res.status(404).json({}); 136 | } 137 | } 138 | }); 139 | }); 140 | 141 | router.get('/spaces/:id', (req, res) => { 142 | res.render('spacedeck', { title: 'Space' }); 143 | }); 144 | 145 | module.exports = router; 146 | -------------------------------------------------------------------------------- /styles/form-input-group.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | .number .slash { 3 | margin-left: -2px; 4 | margin-right: -2px; 5 | top: -2px; 6 | position: relative; 7 | } 8 | .slash { 9 | font-size: 20px; 10 | opacity: 0.4; 11 | color: inherit; 12 | margin-top: -4px; 13 | 14 | margin-left: 5px; 15 | margin-right: 6px; 16 | } 17 | 18 | 19 | .input-couple { 20 | position: relative; 21 | display: inline-block; 22 | vertical-align: middle; 23 | } 24 | 25 | .input-couple { 26 | position: relative; // For dropdowns 27 | display: table; 28 | border-collapse: separate; // prevent input groups from inheriting border styles from table cells when placed within a table 29 | table-layout: fixed; 30 | 31 | &.divided { 32 | > div { 33 | padding-top: 15px ; 34 | display: table-cell; 35 | border-right: 2px solid rgba(0,0,0,0.025); 36 | &:last-child {border: none; } 37 | } 38 | } 39 | 40 | > div { 41 | display: table-cell; 42 | &:first-child .form-group { padding-right: 10px; .label,.input {text-align: right !important;} } 43 | &:last-child .form-group { padding-left: 10px; .label,.input {text-align: left !important;} } 44 | .unit { display: none;} 45 | } 46 | 47 | .slash { 48 | position: absolute; 49 | font-size: 20px; 50 | opacity: 0.4; 51 | right: 3px; 52 | bottom: 0px; 53 | width: 0; 54 | height: 60px; 55 | line-height: 60px; 56 | color: inherit; 57 | } 58 | 59 | .times { 60 | position: absolute; 61 | font-size: 20px; 62 | opacity: 0.4; 63 | right: 7px; 64 | bottom: 0px; 65 | width: 0; 66 | height: 60px; 67 | line-height: 60px; 68 | } 69 | } 70 | 71 | .input-group { 72 | position: relative; // For dropdowns 73 | display: table; 74 | border-collapse: separate; // prevent input groups from inheriting border styles from table cells when placed within a table 75 | 76 | > * { 77 | display: table-cell; 78 | width: 100%; 79 | 80 | } 81 | 82 | .input { 83 | // IE9 fubars the placeholder attribute in text inputs and the arrows on 84 | // select elements in input groups. To fix it, we float the input. Details: 85 | // https://github.com/twbs/bootstrap/issues/11561#issuecomment-28936855 86 | float: left; 87 | width: 100%; 88 | 89 | border-top-right-radius: 0px; 90 | border-bottom-right-radius: 0px; 91 | // border-right: none; 92 | } 93 | .form-group { 94 | padding: 0px; 95 | } 96 | } 97 | 98 | 99 | // Addon and addon wrapper for buttons 100 | .input-group-addon, 101 | .input-group-btn { 102 | width: 1%; 103 | white-space: nowrap; 104 | vertical-align: middle; // Match the inputs 105 | // padding-left: 3px; 106 | .btn { 107 | border-top-left-radius: 0px; 108 | border-bottom-left-radius: 0px; 109 | } 110 | .input { 111 | border-left: none; 112 | } 113 | } 114 | 115 | // Button input groups 116 | // ------------------------- 117 | .input-group-btn { 118 | position: relative; 119 | // Jankily prevent input button groups from wrapping with `white-space` and 120 | // `font-size` in combination with `inline-block` on buttons. 121 | font-size: 0; 122 | white-space: nowrap; 123 | 124 | // Negative margin for spacing, position for bringing hovered/focused/actived 125 | // element above the siblings. 126 | > .btn { 127 | position: relative; 128 | + .btn { 129 | // margin-left: -1px; 130 | } 131 | // Bring the "active" button to the front 132 | &:hover, 133 | &:focus, 134 | &:active { 135 | z-index: 2; 136 | } 137 | } 138 | 139 | // Negative margin to only have a 1px border between the two 140 | &:first-child { 141 | > .btn, 142 | > .btn-group { 143 | // margin-right: -1px; 144 | } 145 | } 146 | &:last-child { 147 | > .btn, 148 | > .btn-group { 149 | // margin-left: -1px; 150 | } 151 | } 152 | } -------------------------------------------------------------------------------- /styles/actions.scss: -------------------------------------------------------------------------------- 1 | @import "vars"; 2 | @import "mixins"; 3 | 4 | #header { 5 | top: 0; 6 | position: fixed; 7 | width: 100%; 8 | z-index: 1000; 9 | padding: $gutter-b; 10 | pointer-events: none; 11 | border-bottom: 1px solid rgba(0,0,0,0.05); 12 | 13 | .home { 14 | margin-top: -20px; 15 | margin-left: -20px; 16 | .icon { 17 | color: $dark; 18 | } 19 | } 20 | .header-left, 21 | .header-right { 22 | pointer-events: auto; 23 | position: relative; 24 | z-index: 10; 25 | > * { 26 | display: inline-block; 27 | vertical-align: middle; 28 | } 29 | } 30 | 31 | 32 | .header-center { 33 | width: 100%; 34 | left: 0; 35 | position: absolute; 36 | text-align: center; 37 | pointer-events: none; 38 | > * { 39 | pointer-events: auto; 40 | 41 | font-size: 20px; 42 | line-height: 44px; 43 | color: $darker; 44 | padding: 0 10px; 45 | } 46 | } 47 | 48 | .header-left > * { margin-right: 10px; } 49 | .header-right > * { margin-left: 5px; } 50 | 51 | .title { 52 | width: 100%; 53 | left: 0; 54 | position: absolute; 55 | text-align: center; 56 | pointer-events: none; 57 | } 58 | 59 | h1 { 60 | margin: 0; 61 | height: 60px; 62 | line-height: 60px; 63 | font-size: $font-size*0.75; 64 | font-weight: bold; 65 | color: $medium; 66 | display: inline-block; 67 | margin-top: -14px; 68 | pointer-events: all; 69 | font-weight: normal; 70 | } 71 | 72 | .author { 73 | float: left; 74 | .btn { 75 | margin-right: 10px; 76 | } 77 | .author-date { 78 | opacity: 0.5; 79 | } 80 | } 81 | } 82 | 83 | .actions { 84 | position: absolute; 85 | top: 0; 86 | @include transition( all 0.25s ease-in-out); 87 | @include backface-visibility(hidden); 88 | z-index: 3000; 89 | opacity: 0; 90 | pointer-events: none; 91 | 92 | .actions { 93 | margin: 0; 94 | } 95 | 96 | @include scale(0,0); 97 | 98 | &.left { left: 0;} 99 | &.right { right: 0;} 100 | 101 | &.in { 102 | @include scale(1,1); 103 | opacity: 1; 104 | * { 105 | pointer-events: auto; 106 | } 107 | } 108 | } 109 | 110 | 111 | #global-actions { 112 | margin: 10px; 113 | text-align: center; 114 | display: inline-block; 115 | position: absolute; 116 | white-space: nowrap; 117 | left: 54px; 118 | top: 0; 119 | .btn { 120 | padding: 0 8px; 121 | &:first-child {padding-left: 20px; } 122 | &:last-child {padding-right: 20px; } 123 | } 124 | } 125 | 126 | #search-wrapper { 127 | position: relative; 128 | display: inline-block; 129 | vertical-align: middle; 130 | .icon { 131 | position: absolute; 132 | right: 5px; 133 | top: 0; 134 | } 135 | #search-input { 136 | text-align: left; 137 | @include transition( width 0.25s ease-in-out, background-color 0.25s ease-in-out); 138 | width: 100px; 139 | &:focus { 140 | width: 250px; 141 | } 142 | } 143 | } 144 | 145 | #home-actions { 146 | width: 100%; 147 | img { 148 | margin: auto; 149 | display: block; 150 | opacity: 0.1; 151 | margin-top: 6px; 152 | } 153 | } 154 | 155 | #no-profile-actions, 156 | #profile-actions { 157 | right: 0; 158 | } 159 | 160 | #user-actions { 161 | #profile-toggle { 162 | background-size: cover; 163 | background-position: center; 164 | } 165 | } 166 | 167 | #space-actions { 168 | right: 0; 169 | .btn { 170 | text-transform: uppercase; 171 | } 172 | #space-version { 173 | position: absolute; 174 | right: 50px; 175 | } 176 | #space-pub-save { 177 | display: inline-block; 178 | position: relative; 179 | vertical-align: middle; 180 | height: 44px; 181 | .btn { 182 | position: absolute; 183 | right: 0; 184 | top: 0; 185 | @include perspective-origin(center right); 186 | } 187 | } 188 | } -------------------------------------------------------------------------------- /views/spacedeck.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Spacedeck Open 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | {% include "./partials/login.html" %} 71 | {% include "./partials/space.html" %} 72 | {% include "./partials/folders.html" %} 73 | {% include "./partials/team.html" %} 74 | {% include "./partials/account.html" %} 75 | {% include "./partials/meta.html" %} 76 | {% include "./partials/meta-folder.html" %} 77 | 78 | {% include "./partials/modal/access.html" %} 79 | {% include "./partials/modal/folder-settings.html" %} 80 | 81 | 82 | 91 | 92 | -------------------------------------------------------------------------------- /helpers/redis.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const config = require('config'); 4 | 5 | // this is a mock version of the Redis API, 6 | // emulating Redis if it is not available locally 7 | var notRedis = { 8 | state: {}, 9 | topics: {}, 10 | 11 | publish: function(topic, msg, cb) { 12 | if (!this.topics[topic]) { 13 | this.topics[topic] = { 14 | subscribers: [] 15 | }; 16 | } 17 | var t=this.topics[topic]; 18 | for (var i=0; i { 119 | cb(); 120 | }); 121 | }, 122 | rateLimit: function(namespace, ip, cb) { 123 | const key = "limit_"+ namespace + "_"+ ip; 124 | const redis = this.connection; 125 | 126 | redis.get(key, (err, count)=> { 127 | if (count) { 128 | if(count < 150) { 129 | redis.incr(key, (err, newCount) => { 130 | if (newCount==150) { 131 | // limit 132 | } 133 | cb(true); 134 | }); 135 | } else { 136 | cb(false); 137 | } 138 | } else { 139 | redis.set(key, 1, (err, count) => { 140 | redis.expire(key, 1800, (err, expResult) => { 141 | cb(true); 142 | }); 143 | }); 144 | } 145 | }); 146 | }, 147 | isOnlineInSpace: function(user, space, cb) { 148 | this.connection.smembers("space_" + space._id.toString(), function(err, list) { 149 | if (err) cb(err); 150 | else { 151 | var users = list.filter(function(item) { 152 | return user._id.toString() === item; 153 | }); 154 | cb(null, (users.length > 0)); 155 | } 156 | }); 157 | } 158 | }; 159 | 160 | return module.exports; 161 | 162 | --------------------------------------------------------------------------------