spacedeck-open/public/javascripts/spacedeck_spaces.js
mntmn ebac854da8
Port Backend to SQLite/Sequelize (removes MongoDB), Support Electron (#14)
* The MongoDB/Mongoose data storage is removed in favor of Sequelize. This abstracts over SQLite or RDBMs like PostgreSQL and MSSQL. The default is SQLite, which significantly simplifies deployments in end-user environments.

* As Spacedeck now has no more mandatory server dependencies, we can wrap it in Electron and ship it as a desktop application.

* Removes docker-compose.yml

* First version of import UI
2018-04-12 16:40:58 +00:00

971 lines
28 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
SpacedeckSpaces
This module contains functions dealing with Spaces UI.
*/
var SpacedeckSpaces = {
data: {
active_space: {advanced:{}},
active_space_loaded: false,
active_space_role: "viewer",
active_space_version_dirty: true,
active_space_messages: [],
active_space_memberships: [],
active_folder_history_items: [],
active_space_users: [],
active_space_artifacts: [],
active_space_path: [],
access_settings_space: null,
access_settings_memberships: [],
duplicate_folders: [],
duplicate_folder_id: "",
pending_pdf_files: [],
meta_visible: false,
meta_unseen: 0,
present_mode: false,
space_editing_title: false,
create_space_title: "",
folder_reverse: 1,
embedded: false,
remix_cta: "Create Reply",
publish_cta: "Publish",
remix_copying: true,
remix_style: "",
guest_signup_enabled: false,
space_embed_html: "",
share_base: location.origin,
share_base_url: location.origin+"/spaces/",
share_base_url_enc: encodeURIComponent(location.origin+"/spaces/"),
social_bar: true,
can_add_comment: false,
space_info_section: "access",
editors_section: "list",
selected_member: null,
invite_member_role: 'viewer',
invite_email_error: null,
invite_email: "",
invite_message: "",
active_join_link: "",
join_link_role: "viewer",
mouse_state: "idle",
// folders
active_folder: null,
folder_sorting: "updated_at",
folder_spaces_filter: null,
active_path_length : 0,
space_comment: "",
folder_spaces_search: "",
// map of artifact IDs to medium rich text editor objects
medium_for_object: {},
},
methods: {
search_spaces: function() {
var query = this.folder_spaces_search;
console.log("search query: ",query);
load_spaces_search(query, function(spaces) {
console.log("results: ",spaces);
this.active_profile_spaces = spaces;
}.bind(this));
},
guest_logout: function() {
if ("localStorage" in window && localStorage) {
delete localStorage['guest_nickname'];
}
this.guest_nickname = "";
location.reload();
},
ask_guestname: function(dft, cb) {
console.log("ask_guestname");
var team_name = "Spacedeck";
if(subdomainTeam) {
team_name = subdomainTeam.name;
}
smoke.prompt(__('what_is_your_name', team_name) , function(content) {
if (!content || (content.length === 0)) {
this.ask_guestname(dft, cb);
} else {
this.guest_nickname = content;
if ("localStorage" in window && localStorage) {
try {
localStorage['guest_nickname'] = this.guest_nickname;
}catch(e) {
console.error(e);
}
}
if (cb) cb();
}
}.bind(this), {value: dft || "Guest "+parseInt(10000*Math.random()), ok: __("ok"), cancel: __("cancel")});
},
load_space: function(space_id, on_success, on_error) {
console.log("load space: ", space_id);
this.folder_spaces_filter="";
this.folder_spaces_search="";
space_auth = get_query_param("spaceAuth");
var userReady = function() {
if (get_query_param("embedded")) {
this.embedded = true;
this.guest_signup_enabled = true;
if (get_query_param("publish_cta")) {
this.publish_cta = get_query_param("publish_cta");
}
if (get_query_param("nosocial")) {
this.social_bar = false;
}
}
if (get_query_param("confirm") && this.logged_in) {
var token = get_query_param("confirm");
confirm_user(this.user, token, function() {
this.redirect_to("/spaces/"+space_id+"?show_access=1");
}.bind(this), function() {
alert("An error occured confirming your email with the given token.");
});
return;
}
this.close_dropdown();
this.active_space_loaded = false;
this.viewport_zoom = 1;
this.viewport_zoom_percent = 100;
this.loading_space_id = space_id;
this.present_mode = false;
this.active_space_is_readonly = true;
this.opened_dialog = "none";
this.open_space_dialog = "none";
this.selected_artifacts_dict = {};
this.update_selection_metrics();
this.can_add_comment = false;
var is_home = false;
if (this.user) {
is_home = (space_id == this.user.home_folder_id);
}
document.title = "Loading… | Spacedeck";
load_space(space_id, function(space, role) {
document.title = space.name;
this.active_space_role = role || "viewer"; //via req header from backend
this.space_embed_html = "<iframe width=\"1024\" height=\"768\" seamless src=\""+ENV.webEndpoint+"/spaces/"+space._id+"?embedded=1\"></iframe>";
if (!is_home) {
//console.log(space);
load_members(space, function(members) {
this.active_space_memberships = members;
}.bind(this));
}
console.log("[websocket] auth start");
if (space.space_type == "folder") {
this.active_space = {advanced:{}};
document.title = "Spacedeck";
load_spaces(space_id, is_home, function(spaces) {
space.children = spaces;
this.loading_space_id = null;
this.active_profile_spaces = space.children;
this.active_folder = space;
this.access_settings_space = space;
this.auth_websocket(this.active_folder);
this.load_space_path(this.active_folder);
if (is_home) {
this.root_folder = space;
}
load_history(space, function(history) {
console.log("loaded digest", history);
this.active_folder_history_items = history;
this.meta_unseen = 0;
if ("localStorage" in window && localStorage) {
var last_seen = parseInt(localStorage[this.meta_last_seen_key()], 10);
} else {
var last_seen = 0;
}
for (var i=0; i<history.length; i++) {
var item = history[i];
var t = new Date(item.last_action).getTime();
var my_own = false;
if (item.users.length==1 && item.users[0]=="you") {
my_own = true;
}
if (t>last_seen && !my_own) this.meta_unseen++;
}
}.bind(this));
this.active_view = "folders";
}.bind(this));
if ("localStorage" in window) {
var key = "folder_sorting_"+space_id;
var key2 = "folder_reverse_"+space_id;
if (localStorage[key] && localStorage[key2]) {
this.folder_sorting = localStorage[key];
this.folder_reverse = parseInt(localStorage[key2]);
console.log("loaded folder sorting: ",this.folder_sorting,this.folder_reverse);
}
}
// legacy fix
if (this.folder_sorting == "opened_at") {
this.folder_sorting = "name";
}
} else if (space.space_type == "space") {
this.artifacts = [];
this.loading_space_id = null;
document.title = space.name;
if (space_auth || this.logged_in) {
this.can_add_comment = true;
}
this.setup_watches();
load_artifacts(space._id, function(artifacts) {
// FIXME: how to cleanly handle this error?
if (!artifacts) {
artifacts = [];
}
// FIXME: remove kludge
for (var i=0; i<artifacts.length; i++) {
this.update_board_artifact_viewmodel(artifacts[i]);
}
this.active_space_artifacts = artifacts;
this.$set("active_space", space);
this.active_space = space;
this.auth_websocket(this.active_space);
this.active_view = "space";
this.fixup_space_size();
if (space._id != this.active_space._id) {
this.present_mode = true;
this.active_space_is_readonly = true;
} else {
this.active_space_is_readonly = false;
}
this.discover_zones();
//window.setTimeout(function() {
// this.zoom_to_fit();
//}.bind(this),10);
if (on_success) {
on_success();
}
this.active_space_loaded = true;
this.extract_properties_from_selection(); // populates zones etc
load_comments(space._id, function(messages) {
if (!messages) messages = [];
this.active_space_messages = messages;
this.refresh_space_comments();
}.bind(this), function(xhr) { console.error(xhr); });
}.bind(this));
if (this.active_space_role == "editor" || this.active_space_role == "admin") {
this.present_mode = false;
this.active_space_is_readonly = false;
}
// FIXME
this.active_join_link = "";
this.join_link_role = "viewer";
// FIXME
if (this.active_space_role == "admin") {
this.space_info_section="access";
} else if (this.active_space_role == "editor") {
//this.space_info_section="versions";
} else {
this.space_info_section="info";
}
}
}.bind(this), function(xhr) {
if (on_error) {
return on_error(xhr);
}
if (xhr.status == 403) {
if (!this.logged_in) {
this.redirect_to("/login?space_id="+space_id);
} else {
this.redirect_to("/");
}
} else {
this.redirect_to("/not_found");
console.error(xhr);
}
}.bind(this));
}.bind(this);
var default_guest = "";
if (("localStorage" in window && localStorage) && localStorage['guest_nickname']) {
this.guest_nickname = localStorage['guest_nickname'];
default_guest = this.guest_nickname;
userReady();
}
if (space_auth) {
if (this.guest_nickname) {
userReady();
} else {
this.ask_guestname(default_guest, function() {
userReady();
});
}
} else {
this.guest_nickname = "";
userReady();
}
},
refresh_space_comments: function() {
this.meta_unseen = 0;
var messages = this.active_space_messages;
var last_seen = 0;
if ("localStorage" in window && localStorage) {
last_seen = parseInt(localStorage[this.meta_last_seen_key()], 10);
}
for (var i=0; i<messages.length; i++) {
var item = messages[i];
var t = new Date(item.updated_at).getTime();
var my_own = false;
if (this.user && this.user._id!=item.user_id && !item.editor_name) {
my_own = true;
}
if (t>last_seen && !my_own) this.meta_unseen++;
}
},
go_to_next_space: function() {
var space_ids = this.active_folder.children.map(function(s){return s._id});
var idx = space_ids.indexOf(this.active_space._id);
console.log("index: ",idx);
var cur_idx = idx;
var done = false;
while (!done) {
var next = this.active_folder.children[(idx+1)%space_ids.length];
if (next.space_type == "folder") {
done = false;
idx++;
} else {
done = true;
}
if (cur_idx == idx) done = true; // wraparound
}
this.load_space(next._id);
},
go_to_previous_space: function() {
var space_ids = this.active_folder.children.map(function(s){return s._id});
var idx = space_ids.indexOf(this.active_space._id);
console.log("index: ",idx);
var cur_idx = idx;
var done = false;
while (!done) {
var idx = (idx<1?space_ids.length:idx)-1;
var prev = this.active_folder.children[idx];
if (prev.space_type == "folder") {
done = false;
idx--;
} else {
done = true;
}
if (cur_idx == idx) done = true; // wraparound
}
this.load_space(prev._id);
},
filtered_folder_children: function(type){
var type = type || "space";
return _.filter(this.active_folder.children, function(s){
return s.space_type == type;
})
},
load_space_path: function(space) {
if (!space) return [];
load_space_path(space._id, function(path) {
this.active_space_path = path;
}.bind(this), function() { console.log("could not load folder path")});
},
is_active_space_role: function(rolename) {
if(!this.active_space) return false;
return this.active_space_role == rolename;
},
create_space: function(space_type) {
if (!this.active_folder) return;
this.close_modal();
this.folder_spaces_filter="";
if (!this.active_folder.children) {
this.active_folder.children = [];
}
if (!space_type) space_type = "space";
var s = {
name: space_type == "space" ? __("untitled_space") : __("untitled_folder") ,
artifacts: [],
space_type: space_type,
parent_space_id: this.active_folder._id
};
if (this.create_space_title.length) {
s.name = this.create_space_title;
}
save_space(s, function(saved_space) {
this.active_folder.children.push(saved_space);
if (space_type != "folder") {
this.redirect_to("/"+saved_space.space_type+"s/"+saved_space._id, function(succ) {
});
} else {
this.rename_folder(saved_space);
}
}.bind(this), function(xhr) {
alert("Error: Could not create Space ("+xhr.status+").");
}.bind(this));
},
save_space: function(s) {
save_space(s);
},
create_space_version: function() {
if (!this.is_pro(this.user)) {
// pro feature
smoke.confirm(__("spacedeck_pro_ad_versions"), function(confirmed) {
if (confirmed) this.show_upgrade_modal();
}.bind(this));
return;
}
this.version_saving = true;
this.present_mode = false;
var s = this.active_space.draft_space;
console.log("create_space_version:", s);
duplicate_space(s, null, function(new_version_space) {
load_spaces(this.active_space._id, false, function(space) {
this.version_saving = false;
this.activate_space_version(space, space.draft_space);
alert("Version saved.");
}.bind(this));
}.bind(this), function(xhr){
console.error(xhr);
}.bind(this));
},
finalize_folder_profile_edit: function() {
save_space(this.active_folder, function(saved_space) {
this.close_modal();
}.bind(this));
},
finalize_space_profile_edit: function() {
save_space(this.active_space, function(saved_space) {
this.close_modal();
}.bind(this));
},
delete_space: function(space) {
smoke.confirm("Really delete "+space.name+"?", function(confirmed) {
if (!confirmed) return;
var idx = this.active_folder.children.indexOf(space);
delete_space(space, function() {
if (space.parent_space_id){
this.redirect_to("/folders/"+space.parent_space_id, function(succ) {});
} else {
this.redirect_to("/spaces", function(succ) {});
}
this.close_modal();
this.active_folder.children.splice(idx,1);
}.bind(this));
}.bind(this));
},
duplicate_space: function(space) {
duplicate_space(space, null, function(new_space) {
//alert("Space duplicated.");
this.active_folder.children.push(new_space);
}.bind(this), function(xhr){
console.error(xhr);
}.bind(this));
},
remove_avatar: function(space) {
remove_avatar_file("space", space, function(s) {
this.active_space = s;
}.bind(this));
},
rename_space: function(space) {
this.close_dropdown();
if (space.space_type == "folder") return this.rename_folder(space);
smoke.prompt(__("new_space_title"), function(title) {
if (title && title.length) {
space.name = title;
save_space(space);
}
}.bind(this), {value: space.name});
},
rename_folder: function(folder) {
this.close_dropdown();
smoke.prompt(__("new_folder_title"), function(title) {
if (title && title.length) {
folder.name = title;
save_space(folder);
}
}.bind(this), {value: folder.name});
},
edit_space_title: function() {
this.close_dropdown();
if (this.active_space_role=="editor" || this.active_space_role=="admin") {
this.space_editing_title = true;
$("#space-title").focus();
}
},
save_space_title: function(name) {
this.active_space.name = name;
save_space(this.active_space, function() {
this.space_editing_title = false;
}.bind(this));
},
save_space_keydown: function($event) {
if ($event) {
if ($event.keyCode != 13) {
this.space_editing_title = true;
return;
}
$event.preventDefault();
$event.stopPropagation();
$event.target.blur();
}
save_space(this.active_space, function(updated_space) {
this.active_space.edit_slug = updated_space.edit_slug;
this.space_editing_title = false;
}.bind(this));
},
save_space_description: function($event) {
$event.preventDefault();
$event.stopPropagation();
var val = $event.target.innerText;
$event.target.blur();
this.active_space.description = val;
save_space(this.active_space);
},
save_space_domain: function($event) {
$event.preventDefault();
$event.stopPropagation();
var val = $event.target.innerText;
$event.target.blur();
this.active_space.domain = val;
save_space(this.active_space);
},
download_space: function() {
smoke.quiz(__("download_space"), function(e, test) {
if (e == "PDF") {
this.download_space_as_pdf(this.active_space);
} else if (e == "ZIP") {
this.download_space_as_zip(this.active_space);
}
}.bind(this), {
button_1: "PDF",
button_2: "ZIP",
button_cancel:__("cancel")
});
},
download_space_as_png: function(space) {
window.open(ENV.apiEndpoint + "/api/spaces/" + space._id + "/png");
},
download_space_as_pdf: function(space) {
this.global_spinner = true;
get_resource("/spaces/" + space._id + "/pdf", function(o) {
this.global_spinner = false;
location.href = o.url;
}.bind(this), function(xhr) {
this.global_spinner = false;
alert("PDF export problem (" + xhr.status + ").");
}.bind(this));
},
download_space_as_zip: function(space) {
this.global_spinner = true;
get_resource("/spaces/" + space._id + "/zip", function(o) {
this.global_spinner = false;
location.href = o.url;
}.bind(this), function(xhr) {
this.global_spinner = false;
alert("ZIP export problem (" + xhr.status + ").");
}.bind(this));
},
download_space_as_list: function(space) {
this.global_spinner = true;
location.href = "/api/spaces/" + space._id + "/list";
},
duplicate_space_into_folder: function() {
load_writable_folders( function(folders){
this.duplicate_folders = _.sortBy(folders, function (folder) { return folder.name; });
}.bind(this), function(xhr) {
console.error(xhr);
});
},
duplicate_folder_confirm: function() {
var folderId = this.duplicate_folder_id;
var idx = _.findIndex(this.duplicate_folders, function(s) { return s._id == folderId;});
if (idx<0) idx = 0;
var folder = this.duplicate_folders[idx];
console.log("df f",folder);
if (!folder) return;
duplicate_space(this.active_space, folder._id, function(new_space) {
this.duplicate_folders = [];
this.duplicate_folder = null;
smoke.quiz(__("duplicate_success", this.active_space.name, folder.name), function(e, test){
if (e == __("goto_space", new_space.name)){
this.redirect_to("/spaces/" + new_space._id);
}else if (e == __("goto_folder", folder.name)){
this.redirect_to("/folders/" + folder._id);
}
}.bind(this), {
button_1: __("goto_space", new_space.name),
button_2: __("goto_folder", folder.name),
button_cancel:__("stay_here")
});
}.bind(this), function(xhr){
console.error(xhr);
smoke.prompt("error: " + xhr.statusText);
}.bind(this));
},
toggle_follow_mode: function() {
this.deselect();
this.follow_mode = !this.follow_mode;
},
toggle_present_mode: function() {
this.deselect();
this.present_mode = !this.present_mode;
if (this.present_mode) {
//this.go_to_first_zone();
}
},
meta_last_seen_key: function() {
var seen_key = "meta-seen-";
if (this.active_view == 'space') {
if (!this.active_space) return "invalid";
seen_key += this.active_space._id;
} else if (this.active_view == 'folders') {
if (!this.active_folder) return "invalid";
seen_key += this.active_folder._id;
}
return seen_key;
},
toggle_meta: function() {
this.meta_visible = !this.meta_visible;
if (this.meta_visible) {
var seen_key = this.meta_last_seen_key();
if ("localStorage" in window && localStorage) {
localStorage[seen_key] = new Date().getTime();
console.log("seen_key: ",seen_key,localStorage[seen_key]);
this.meta_last_seen = localStorage[seen_key];
}
this.meta_unseen = 0;
}
},
toggle_space_access_mode: function() {
this.access_settings_space.access_mode = (this.access_settings_space.access_mode=="public")?"private":"public";
save_space(this.access_settings_space);
},
save_space_access_mode: function(evt) {
// FIXME really bad that i have to do this manually. why is the
// value not already updated when v-on="change" is fired?!
this.access_settings_space.access_mode = evt.currentTarget.value;
save_space(this.access_settings_space);
},
save_space_editors_locking: function(evt) {
// FIXME same issue as above
this.access_settings_space.editors_locking = evt.currentTarget.checked;
save_space(this.access_settings_space);
},
create_join_link: function() {
create_join_link(this.active_space._id, this.join_link_role, function(result) {
this.active_join_link = "https://"+location.host+"/invitations/"+result.code+"/accept";
}.bind(this));
},
delete_join_link: function() {
get_join_link(this.active_space._id, function(result) {
if (result && result.length) {
delete_join_link(result[result.length-1]._id, function() {
this.active_join_link = "";
}.bind(this));
}
}.bind(this));
},
invite_member: function(space, emails_text, txt, role) {
this.invite_email_error = null;
var emails = emails_text.split(",");
var displayed_success = false;
_.each(emails, function(email) {
email = email.trim();
if (!validateEmail(email)) {
this.invite_email_error = "Please enter a valid address."
return;
}
var m = {
email_invited: email,
personal_message: txt,
role: role
}
create_membership(space, m, function(m) {
this.access_settings_memberships.push(m);
console.log("membership created:", m);
this.editors_section="list";
if (!displayed_success) {
displayed_success = true;
smoke.alert("Invitation(s) sent.");
this.invite_email = "";
this.invite_message = "";
}
}.bind(this), function(xhr){
text = JSON.stringify(xhr.responseText);
smoke.alert("Error: "+text);
}.bind(this));
}.bind(this));
},
update_member: function(space, m, role) {
m.role = role;
save_membership(space, m, function() {
console.log("saved")
}.bind(this), function(xhr) {
console.error(xhr);
}.bind(this));
},
// revoke
remove_member: function(space, m) {
delete_membership(space, m, function() {
this.access_settings_memberships.splice(this.access_settings_memberships.indexOf(m), 1);
}.bind(this), function(xhr) {
console.error(xhr);
}.bind(this));
},
history_back: function() {
window.history.back();
},
create_space_comment: function(comment) {
if (!comment.length) return;
var data = {
space: this.active_space._id,
message: comment,
editor_name: this.guest_nickname,
user: this.user
};
save_comment(this.active_space._id, data, function(comment) {
console.log("comment saved: ",comment.created_at);
this.active_space_messages.push(comment);
this.space_comment = "";
}.bind(this), function(xhr){
console.error(xhr);
}.bind(this));
},
remove_space_comment: function(comment) {
delete_comment(this.active_space._id, comment._id, function() {
console.log("comment id:",comment._id);
this.active_space_messages = _.filter(this.active_space_messages, function(c){return c._id!=comment._id;});
}.bind(this), function(xhr){
console.error(xhr);
}.bind(this));
},
emojified_comment: function(comment) {
return twemoji.parse(comment);
},
set_folder_sorting: function(key,reverse) {
this.folder_sorting = key;
this.folder_reverse = reverse?-1:1;
console.log(key, reverse);
if ("localStorage" in window) {
localStorage["folder_sorting_"+this.active_folder._id] = this.folder_sorting;
localStorage["folder_reverse_"+this.active_folder._id] = this.folder_reverse;
}
},
activate_space_info_section: function(id) {
this.space_info_section = id;
this.editors_section = "list";
if (id == "versions") {
// load space versions
load_spaces(this.active_space._id, null, function(space_with_children) {
this.active_space.children = space_with_children.children;
console.log("loaded: ",space_with_children);
}.bind(this));
} else if (id == "info") {
// load replies
}
},
handle_folder_drop: function(evt, dest) {
try {
var source = JSON.parse(evt.dataTransfer.getData("application/json"));
} catch (e) {
return;
}
if (!source || !source._id || !source.parent_space_id || !dest._id) return;
if (source._id==dest._id) return;
if (dest.space_type!="folder") {
alert("Spaces can only be moved into folders.");
return;
}
source.parent_space_id = dest._id;
save_space(source, function() {
var idx = _.findIndex(this.active_folder.children, function(s) { return s._id == source._id;});
if (idx>=0) {
this.active_folder.children.splice(idx,1);
console.log("spliced: ",idx);
}
}.bind(this));
},
activate_access: function() {
this.activate_modal("access");
//this.meta_visible = false;
if (this.active_space._id) {
this.access_settings_space = this.active_space;
} else if (this.active_folder && this.active_folder._id) {
this.access_settings_space = this.active_folder;
} else {
return;
}
this.access_settings_memberships = this.active_space_memberships;
},
close_access: function() {
this.close_modal();
},
show_offline_help: function() {
smoke.confirm(__('was_offline'), function(confirmed) {
if (!confirmed) return;
location.reload();
});
}
}
}