Files
spacedeck-open/public/javascripts/spacedeck_spaces.js
mntmn 0c5fa597e8 Allow embedding of folders and access to folders to anonymous editors with edit_hash/spaceAuth links (#63)
* add subspaces to be listed with edit_hash/spaceAuth authorization

* remove dead code from api_helpers.js

* add edit_hash authorization for requested space thumbnails

* handle /s/:hash links in frontend router

* set space_auth via a function, allow passing it to load_space

* rename variable in /s/:hash router in backend

* hide search, profile, breadcrumb in folders if not logged in, construct links to subspaces differently for anonymous editors
2020-06-02 20:47:58 +02:00

905 lines
26 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: [],
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;
load_spaces_search(query, function(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) {
smoke.prompt(__('what_is_your_name', "Spacedeck") , 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, space_auth) {
this.folder_spaces_filter="";
this.folder_spaces_search="";
if (space_auth) {
set_space_auth(space_auth);
} else {
set_space_auth(get_query_param("spaceAuth"));
}
this.embedded = !!(get_query_param("embedded"));
var userReady = function() {
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
if (!is_home) {
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";
}
}.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 (!this.user && 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";
},
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();
if (this.embedded) {
document.documentElement.requestFullscreen();
}
} else {
if (this.embedded) {
document.exitFullscreen();
}
}
},
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){
try {
var res = JSON.parse(xhr.response);
alert("Error: "+res.error);
} catch (e) {
console.error(e, xhr);
}
}.bind(this));
}.bind(this));
},
update_member: function(space, m, role) {
m.role = role;
save_membership(space, m, function() {
}.bind(this), function(xhr) {
try {
var res = JSON.parse(xhr.response);
alert("Error: "+res.error);
} catch (e) {
console.error(e, 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) {
try {
var res = JSON.parse(xhr.response);
alert("Error: "+res.error);
} catch (e) {
console.error(e, 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));
},
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();
});
}
}
}