├── 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 | [[__("created")]]
8 | [[__("name")]]
9 | [[__("link")]]
10 |
11 | {% for s in subspaces %}
12 |
13 | [[ s.created_at | date('d.m.Y H:i') ]]
14 | [[ s.name ]]
15 | [[ s.ae_link ]]
16 |
17 | {% endfor %}
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/views/partials/tool/text-digits.html:
--------------------------------------------------------------------------------
1 |
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 |
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 |
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 | created
8 | updated
9 | filetype
10 | filename
11 | preview
12 |
13 | {% for a in space.artifacts %}
14 |
15 | [[ a.created_at | date('d.m.Y H:i') ]] by [[ a.user.email ]][[ a.editor_name ]]
16 | [[ a.updated_at | date('d.m.Y H:i') ]] by [[ a.update_user.email ]][[ a.last_update_editor_name ]]
17 | [[ a.mime ]]
18 | {% if a.payload_uri %}[[ a.filename ]] {% endif %}
19 | [[ a.description ]]
20 |
21 | {% endfor %}
22 |
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 |
6 |
7 |
8 |
9 |
Click to Upload or drag file(s) anywhere.
10 |
11 |
12 |
13 |
14 |
Insert Image
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 |
7 |
8 |
9 |
10 |
Click to Upload or drag file(s) here
11 |
12 |
13 |
14 |
15 |
Insert Audio
16 |
17 |
--------------------------------------------------------------------------------
/views/partials/tool/video.html:
--------------------------------------------------------------------------------
1 | Video
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
Click to Upload or drag file(s) anywhere.
10 |
11 |
12 |
13 |
14 |
Insert Video
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 |
6 |
7 | Grid
8 |
9 |
10 |
11 |
12 |
13 |
14 | Canvas
15 |
16 |
17 |
18 |
19 |
20 |
21 | Article
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/views/partials/tool/crop.html:
--------------------------------------------------------------------------------
1 | Crop (Not yet implemented)
2 |
3 |
--------------------------------------------------------------------------------
/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 | [[__("add_zone")]]
10 |
11 |
12 |
13 | {{{z.description}}}
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 |
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 |
6 |
7 | Edit
8 |
9 |
10 |
11 |
12 |
13 |
14 | Group
15 |
16 |
17 |
18 |
19 |
20 |
21 | Copy
22 |
23 |
24 |
25 |
26 |
27 |
28 | Delete
29 |
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 |
21 | Moodboards
22 | Collages
23 | Teaching (Virtual Blackboards)
24 | Shared Whiteboards
25 | Design Thinking
26 |
27 |
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 | Gutter
17 |
18 |
19 |
20 |
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 | Grid
5 |
6 |
7 |
8 |
9 | px
10 |
11 |
12 |
13 |
14 |
15 | Subdivision
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | Close
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/views/partials/tool/text-styles.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 | Bold
10 |
11 |
12 |
13 | Italic
14 |
15 |
16 |
17 | Underl.
18 |
19 |
20 |
21 | Strike
22 |
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 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
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 |
--------------------------------------------------------------------------------
/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 |
2 |
3 |
4 |
5 |
11 |
12 |
13 |
14 |
15 | [[__("title")]]
16 |
17 |
18 |
19 |
20 |
21 |
22 |
25 |
26 |
27 |
28 |
29 | [[__("upload_cover_image")]]
30 |
31 |
32 |
33 |
34 |
35 |
[[__("access_caption")]]
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
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 |
4 |
5 | [[__("delete")]]
6 |
7 |
8 |
9 |
10 |
11 |
12 | [[__("lock")]]
13 |
14 |
15 |
16 |
17 | [[__("unlock")]]
18 |
19 |
20 |
21 |
22 | [[__("copy")]]
23 |
24 |
25 |
26 |
27 | [[__("link")]]
28 |
29 |
30 |
31 |
32 |
33 | [[__("download")]]
34 |
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 |
35 |
36 | {% block content %}{% endblock %}
37 |
38 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/views/partials/tool/shapes.html:
--------------------------------------------------------------------------------
1 | [[__("tool_shape")]]
2 |
3 |
4 |
5 |
6 |
7 |
8 | [[__("tool_circle")]]
9 |
10 |
11 |
12 |
13 | [[__("tool_hexagon")]]
14 |
15 |
16 |
17 |
18 | [[__("tool_square")]]
19 |
20 |
21 |
22 |
23 | [[__("tool_bubble")]]
24 |
25 |
26 |
27 |
28 | [[__("tool_cloud")]]
29 |
30 |
31 |
32 |
33 | [[__("tool_burst")]]
34 |
35 |
36 |
37 |
38 | [[__("tool_star")]]
39 |
40 |
41 |
42 |
43 | [[__("tool_heart")]]
44 |
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 |
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 |
14 |
15 |
25 |
26 |
36 |
37 |
47 |
48 |
58 |
59 |
69 |
70 |
71 |
72 | Reset
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 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/views/partials/tool/stroke.html:
--------------------------------------------------------------------------------
1 | Stroke
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | px
11 |
12 |
13 |
14 |
15 |
16 | Border Radius
17 |
18 |
19 |
20 |
21 | px
22 |
23 |
24 |
25 |
41 |
42 |
46 |
47 | Remove Stroke
48 |
49 |
50 |
51 |
52 |
68 |
69 |
--------------------------------------------------------------------------------
/views/partials/tool/pattern.html:
--------------------------------------------------------------------------------
1 | Pattern
2 |
3 |
4 |
5 |
6 |
7 | Upload Image
8 |
9 |
10 | Repeat X-Axis
11 |
19 |
20 |
21 |
22 |
37 |
38 |
47 |
48 |
--------------------------------------------------------------------------------
/views/partials/tool/toolbar-text.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {{active_text_format_name}}
8 |
9 |
10 |
11 |
12 | {% include "./text-formats.html" %}
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | {{active_style.font_family}}
21 | /
22 | {{active_style.font_size}}px
23 |
24 |
25 |
26 | {% include "./text-digits.html" %}
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | Align
36 |
37 |
38 |
39 | {% include "./text-align.html" %}
40 |
41 |
42 |
43 |
44 |
45 |
46 | styles
47 |
48 |
49 |
50 | {% include "./text-styles.html" %}
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | Bullets
59 |
60 |
61 |
62 | Number
63 |
64 |
65 |
66 |
67 |
68 |
69 | Cancel
70 |
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 |
2 |
3 |
4 |
5 |
6 | Google
7 |
8 |
9 |
10 |
11 |
12 | Youtube
13 |
14 |
15 |
16 |
17 |
18 | Tumblr
19 |
20 |
21 |
22 |
23 |
24 | Soundcloud
25 |
26 |
27 |
28 |
29 |
40 |
41 |
42 | Searching...
43 |
44 |
45 |
46 |
47 |
52 |
53 |
54 |
59 |
60 |
61 |
70 |
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 | [[__("email")]]
57 | [[__("name")]]
58 | [[__("role")]]
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | {{u.email}}
69 |
70 |
71 |
72 | {{u.nickname}}
73 |
74 |
75 |
76 | [[__("role_admin")]]
77 | [[__("role_member")]]
78 |
79 |
80 |
81 | [[__("remove")]]
82 | [[__("promote")]]
83 | [[__("demote")]]
84 |
85 |
86 |
87 |
88 |
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 |
--------------------------------------------------------------------------------
41 |-
42 |
43 | {{item.user.nickname}}
44 | {{item.editor_name}}
45 |
46 |
47 |
48 | -
53 | ✕
54 |
55 |
56 | 57 | 58 | 59 |