Merge branch 'develop' of github.com:metamaps/metamaps into window.resize.fix
This commit is contained in:
commit
ad1889dfc5
142 changed files with 4860 additions and 18406 deletions
|
@ -1,6 +1,11 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
"sourceType": "module",
|
"sourceType": "module",
|
||||||
"parser": "babel-eslint",
|
"parser": "babel-eslint",
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaFeatures": {
|
||||||
|
"jsx": true
|
||||||
|
}
|
||||||
|
},
|
||||||
"extends": "standard",
|
"extends": "standard",
|
||||||
"installedESLint": true,
|
"installedESLint": true,
|
||||||
"env": {
|
"env": {
|
||||||
|
@ -13,6 +18,8 @@ module.exports = {
|
||||||
"react"
|
"react"
|
||||||
],
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
|
"react/jsx-uses-react": [2],
|
||||||
|
"react/jsx-uses-vars": [2],
|
||||||
"yoda": [2, "never", { "exceptRange": true }]
|
"yoda": [2, "never", { "exceptRange": true }]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,6 @@ export SECRET_KEY_BASE='267c8a84f63963282f45bc3010eaddf027abfab58fc759d6e239c800
|
||||||
# export S3_BUCKET_NAME
|
# export S3_BUCKET_NAME
|
||||||
# export AWS_ACCESS_KEY_ID
|
# export AWS_ACCESS_KEY_ID
|
||||||
# export AWS_SECRET_ACCESS_KEY
|
# export AWS_SECRET_ACCESS_KEY
|
||||||
# export SSO_KEY
|
|
||||||
#
|
#
|
||||||
# export SMTP_DOMAIN
|
# export SMTP_DOMAIN
|
||||||
# export SMTP_PASSWORD
|
# export SMTP_PASSWORD
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
if ENV['COVERAGE'] == 'on'
|
if ENV['COVERAGE'] == 'on'
|
||||||
SimpleCov.start 'rails'
|
SimpleCov.start 'rails' do
|
||||||
|
add_group 'Policies', 'app/policies'
|
||||||
|
add_group 'Services', 'app/services'
|
||||||
|
add_group 'Serializers', 'app/serializers'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
2
Gemfile
2
Gemfile
|
@ -25,10 +25,8 @@ gem 'rack-cors'
|
||||||
gem 'redis'
|
gem 'redis'
|
||||||
gem 'slack-notifier'
|
gem 'slack-notifier'
|
||||||
gem 'snorlax'
|
gem 'snorlax'
|
||||||
gem 'uservoice-ruby'
|
|
||||||
|
|
||||||
# asset stuff
|
# asset stuff
|
||||||
gem 'coffee-rails'
|
|
||||||
gem 'jquery-rails'
|
gem 'jquery-rails'
|
||||||
gem 'jquery-ui-rails'
|
gem 'jquery-ui-rails'
|
||||||
gem 'sass-rails'
|
gem 'sass-rails'
|
||||||
|
|
17
Gemfile.lock
17
Gemfile.lock
|
@ -70,13 +70,6 @@ GEM
|
||||||
cocaine (0.5.8)
|
cocaine (0.5.8)
|
||||||
climate_control (>= 0.0.3, < 1.0)
|
climate_control (>= 0.0.3, < 1.0)
|
||||||
coderay (1.1.1)
|
coderay (1.1.1)
|
||||||
coffee-rails (4.2.1)
|
|
||||||
coffee-script (>= 2.2.0)
|
|
||||||
railties (>= 4.0.0, < 5.2.x)
|
|
||||||
coffee-script (2.4.1)
|
|
||||||
coffee-script-source
|
|
||||||
execjs
|
|
||||||
coffee-script-source (1.10.0)
|
|
||||||
concurrent-ruby (1.0.2)
|
concurrent-ruby (1.0.2)
|
||||||
debug_inspector (0.0.2)
|
debug_inspector (0.0.2)
|
||||||
delayed_job (4.1.2)
|
delayed_job (4.1.2)
|
||||||
|
@ -103,7 +96,6 @@ GEM
|
||||||
actionmailer (>= 4.0, < 6)
|
actionmailer (>= 4.0, < 6)
|
||||||
activesupport (>= 4.0, < 6)
|
activesupport (>= 4.0, < 6)
|
||||||
execjs (2.7.0)
|
execjs (2.7.0)
|
||||||
ezcrypto (0.7.2)
|
|
||||||
factory_girl (4.7.0)
|
factory_girl (4.7.0)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
factory_girl_rails (4.7.0)
|
factory_girl_rails (4.7.0)
|
||||||
|
@ -145,7 +137,6 @@ GEM
|
||||||
nokogiri (1.6.8)
|
nokogiri (1.6.8)
|
||||||
mini_portile2 (~> 2.1.0)
|
mini_portile2 (~> 2.1.0)
|
||||||
pkg-config (~> 1.1.7)
|
pkg-config (~> 1.1.7)
|
||||||
oauth (0.5.1)
|
|
||||||
orm_adapter (0.5.0)
|
orm_adapter (0.5.0)
|
||||||
paperclip (5.1.0)
|
paperclip (5.1.0)
|
||||||
activemodel (>= 4.2.0)
|
activemodel (>= 4.2.0)
|
||||||
|
@ -262,10 +253,6 @@ GEM
|
||||||
uglifier (3.0.2)
|
uglifier (3.0.2)
|
||||||
execjs (>= 0.3.0, < 3)
|
execjs (>= 0.3.0, < 3)
|
||||||
unicode-display_width (1.1.1)
|
unicode-display_width (1.1.1)
|
||||||
uservoice-ruby (0.0.11)
|
|
||||||
ezcrypto (>= 0.7.2)
|
|
||||||
json (>= 1.7.5)
|
|
||||||
oauth (>= 0.4.7)
|
|
||||||
warden (1.2.6)
|
warden (1.2.6)
|
||||||
rack (>= 1.0)
|
rack (>= 1.0)
|
||||||
websocket-driver (0.6.4)
|
websocket-driver (0.6.4)
|
||||||
|
@ -282,7 +269,6 @@ DEPENDENCIES
|
||||||
better_errors
|
better_errors
|
||||||
binding_of_caller
|
binding_of_caller
|
||||||
brakeman
|
brakeman
|
||||||
coffee-rails
|
|
||||||
delayed_job
|
delayed_job
|
||||||
delayed_job_active_record
|
delayed_job_active_record
|
||||||
devise
|
devise
|
||||||
|
@ -315,10 +301,9 @@ DEPENDENCIES
|
||||||
snorlax
|
snorlax
|
||||||
tunemygc
|
tunemygc
|
||||||
uglifier
|
uglifier
|
||||||
uservoice-ruby
|
|
||||||
|
|
||||||
RUBY VERSION
|
RUBY VERSION
|
||||||
ruby 2.3.0p0
|
ruby 2.3.0p0
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
1.13.2
|
1.13.3
|
||||||
|
|
15
README.md
15
README.md
|
@ -11,9 +11,13 @@ You can find a version of this software running at [metamaps.cc][site-beta], whe
|
||||||
|
|
||||||
Metamaps is created and maintained by a distributed, nomadic community comprised of technologists, artists and storytellers. You can get in touch by using whichever of these channels you prefer:
|
Metamaps is created and maintained by a distributed, nomadic community comprised of technologists, artists and storytellers. You can get in touch by using whichever of these channels you prefer:
|
||||||
|
|
||||||
## Community
|
## How do I learn more?
|
||||||
|
|
||||||
- To send us a personal message or request an invite to the open beta, get in touch with us at team@metamaps.cc or @metamapps on Twitter.
|
- Contact: [team@metamaps.cc](mailto:team@metamaps.cc) or [@metamapps](https://twitter.com/metamapps) on Twitter
|
||||||
|
- User Documentation: [docs.metamaps.cc](https://docs.metamaps.cc)
|
||||||
|
- User Community: [hylo.com/c/metamaps](https://www.hylo.com/c/metamaps)
|
||||||
|
- Development Roadmap: [github.com/metamaps/metamaps/milestones](https://github.com/metamaps/metamaps/milestones)
|
||||||
|
- To send us a personal message or request an invite to the open beta, get in touch with us via email, Twitter, or Hylo
|
||||||
- If you would like to report a bug, please check the [issues][contributing-issues] section in our [contributing instructions][contributing].
|
- If you would like to report a bug, please check the [issues][contributing-issues] section in our [contributing instructions][contributing].
|
||||||
- If you would like to get set up as a developer, that's great! Read on for help getting your development environment set up.
|
- If you would like to get set up as a developer, that's great! Read on for help getting your development environment set up.
|
||||||
|
|
||||||
|
@ -51,10 +55,6 @@ We haven't set up instructions for using Vagrant on Windows, but there are instr
|
||||||
|
|
||||||
- [For Windows][windows-installation]
|
- [For Windows][windows-installation]
|
||||||
|
|
||||||
## Contributing guidelines
|
|
||||||
|
|
||||||
Cloning this repository directly is primarily for those wishing to contribute to our codebase. Check out our [contributing instructions][contributing] to get involved.
|
|
||||||
|
|
||||||
## Licensing information
|
## Licensing information
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or(at your option) any later version.
|
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or(at your option) any later version.
|
||||||
|
@ -65,7 +65,6 @@ The license can be read [here][license].
|
||||||
|
|
||||||
Copyright (c) 2016 Connor Turland
|
Copyright (c) 2016 Connor Turland
|
||||||
|
|
||||||
[site-blog]: http://blog.metamaps.cc
|
|
||||||
[site-beta]: http://metamaps.cc
|
[site-beta]: http://metamaps.cc
|
||||||
[license]: https://github.com/metamaps/metamaps/blob/develop/LICENSE
|
[license]: https://github.com/metamaps/metamaps/blob/develop/LICENSE
|
||||||
[contributing]: https://github.com/metamaps/metamaps/blob/develop/doc/CONTRIBUTING.md
|
[contributing]: https://github.com/metamaps/metamaps/blob/develop/doc/CONTRIBUTING.md
|
||||||
|
|
BIN
app/assets/images/import-example.png
Normal file
BIN
app/assets/images/import-example.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 44 KiB |
BIN
app/assets/images/import.png
Normal file
BIN
app/assets/images/import.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 325 B |
BIN
app/assets/images/junto.gif
Normal file
BIN
app/assets/images/junto.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
BIN
app/assets/images/view-only.png
Normal file
BIN
app/assets/images/view-only.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 421 B |
|
@ -16,4 +16,3 @@
|
||||||
//= require_directory ./lib
|
//= require_directory ./lib
|
||||||
//= require ./src/Metamaps.Erb
|
//= require ./src/Metamaps.Erb
|
||||||
//= require ./webpacked/metamaps.bundle
|
//= require ./webpacked/metamaps.bundle
|
||||||
//= require ./src/check-canvas-support
|
|
||||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -1,39 +0,0 @@
|
||||||
var attachMediaStream = function (stream, el, options) {
|
|
||||||
var URL = window.URL;
|
|
||||||
var opts = {
|
|
||||||
autoplay: true,
|
|
||||||
mirror: false,
|
|
||||||
muted: false
|
|
||||||
};
|
|
||||||
var element = el || document.createElement('video');
|
|
||||||
var item;
|
|
||||||
|
|
||||||
if (options) {
|
|
||||||
for (item in options) {
|
|
||||||
opts[item] = options[item];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opts.autoplay) element.autoplay = 'autoplay';
|
|
||||||
if (opts.muted) element.muted = true;
|
|
||||||
if (opts.mirror) {
|
|
||||||
['', 'moz', 'webkit', 'o', 'ms'].forEach(function (prefix) {
|
|
||||||
var styleName = prefix ? prefix + 'Transform' : 'transform';
|
|
||||||
element.style[styleName] = 'scaleX(-1)';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// this first one should work most everywhere now
|
|
||||||
// but we have a few fallbacks just in case.
|
|
||||||
if (URL && URL.createObjectURL) {
|
|
||||||
element.src = URL.createObjectURL(stream);
|
|
||||||
} else if (element.srcObject) {
|
|
||||||
element.srcObject = stream;
|
|
||||||
} else if (element.mozSrcObject) {
|
|
||||||
element.mozSrcObject = stream;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return element;
|
|
||||||
};
|
|
685
app/assets/javascripts/lib/best_in_place.js
Normal file
685
app/assets/javascripts/lib/best_in_place.js
Normal file
|
@ -0,0 +1,685 @@
|
||||||
|
/*
|
||||||
|
* BestInPlace (for jQuery)
|
||||||
|
* version: 3.0.0.alpha (2014)
|
||||||
|
*
|
||||||
|
* By Bernat Farrero based on the work of Jan Varwig.
|
||||||
|
* Examples at http://bernatfarrero.com
|
||||||
|
*
|
||||||
|
* Licensed under the MIT:
|
||||||
|
* http://www.opensource.org/licenses/mit-license.php
|
||||||
|
*
|
||||||
|
* @requires jQuery
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
*
|
||||||
|
* Attention.
|
||||||
|
* The format of the JSON object given to the select inputs is the following:
|
||||||
|
* [["key", "value"],["key", "value"]]
|
||||||
|
* The format of the JSON object given to the checkbox inputs is the following:
|
||||||
|
* ["falseValue", "trueValue"]
|
||||||
|
|
||||||
|
*/
|
||||||
|
//= require jquery.autosize
|
||||||
|
|
||||||
|
function BestInPlaceEditor(e) {
|
||||||
|
'use strict';
|
||||||
|
this.element = e;
|
||||||
|
this.initOptions();
|
||||||
|
this.bindForm();
|
||||||
|
this.initPlaceHolder();
|
||||||
|
jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
BestInPlaceEditor.prototype = {
|
||||||
|
// Public Interface Functions //////////////////////////////////////////////
|
||||||
|
|
||||||
|
activate: function () {
|
||||||
|
'use strict';
|
||||||
|
var to_display;
|
||||||
|
if (this.isPlaceHolder()) {
|
||||||
|
to_display = "";
|
||||||
|
} else if (this.original_content) {
|
||||||
|
to_display = this.original_content;
|
||||||
|
} else {
|
||||||
|
switch (this.formType) {
|
||||||
|
case 'input':
|
||||||
|
case 'textarea':
|
||||||
|
if (this.display_raw) {
|
||||||
|
to_display = this.element.html().replace(/&/gi, '&');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var value = this.element.data('bipValue');
|
||||||
|
if (typeof value === 'undefined') {
|
||||||
|
to_display = '';
|
||||||
|
} else if (typeof value === 'string') {
|
||||||
|
to_display = this.element.data('bipValue').replace(/&/gi, '&');
|
||||||
|
} else {
|
||||||
|
to_display = this.element.data('bipValue');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'select':
|
||||||
|
to_display = this.element.html();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.oldValue = this.isPlaceHolder() ? "" : this.element.html();
|
||||||
|
this.display_value = to_display;
|
||||||
|
jQuery(this.activator).unbind("click", this.clickHandler);
|
||||||
|
this.activateForm();
|
||||||
|
this.element.trigger(jQuery.Event("best_in_place:activate"));
|
||||||
|
},
|
||||||
|
|
||||||
|
abort: function () {
|
||||||
|
'use strict';
|
||||||
|
this.activateText(this.oldValue);
|
||||||
|
jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
|
||||||
|
this.element.trigger(jQuery.Event("best_in_place:abort"));
|
||||||
|
this.element.trigger(jQuery.Event("best_in_place:deactivate"));
|
||||||
|
},
|
||||||
|
|
||||||
|
abortIfConfirm: function () {
|
||||||
|
'use strict';
|
||||||
|
if (!this.useConfirm) {
|
||||||
|
this.abort();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (confirm(BestInPlaceEditor.defaults.locales[''].confirmMessage)) {
|
||||||
|
this.abort();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
update: function () {
|
||||||
|
'use strict';
|
||||||
|
var editor = this,
|
||||||
|
value = this.getValue();
|
||||||
|
|
||||||
|
// Avoid request if no change is made
|
||||||
|
if (this.formType in {"input": 1, "textarea": 1} && value === this.oldValue) {
|
||||||
|
this.abort();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.ajax({
|
||||||
|
"type": this.requestMethod(),
|
||||||
|
"dataType": BestInPlaceEditor.defaults.ajaxDataType,
|
||||||
|
"data": editor.requestData(),
|
||||||
|
"success": function (data, status, xhr) {
|
||||||
|
editor.loadSuccessCallback(data, status, xhr);
|
||||||
|
},
|
||||||
|
"error": function (request, error) {
|
||||||
|
editor.loadErrorCallback(request, error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
switch (this.formType) {
|
||||||
|
case "select":
|
||||||
|
this.previousCollectionValue = value;
|
||||||
|
|
||||||
|
// search for the text for the span
|
||||||
|
$.each(this.values, function(index, arr){ if (String(arr[0]) === String(value)) editor.element.html(arr[1]); });
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "checkbox":
|
||||||
|
$.each(this.values, function(index, arr){ if (String(arr[0]) === String(value)) editor.element.html(arr[1]); });
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (value !== "") {
|
||||||
|
if (this.display_raw) {
|
||||||
|
editor.element.html(value);
|
||||||
|
} else {
|
||||||
|
editor.element.text(value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
editor.element.html(this.placeHolder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.element.data('bipValue', value);
|
||||||
|
editor.element.attr('data-bip-value', value);
|
||||||
|
|
||||||
|
editor.element.trigger(jQuery.Event("best_in_place:update"));
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
activateForm: function () {
|
||||||
|
'use strict';
|
||||||
|
alert(BestInPlaceEditor.defaults.locales[''].uninitializedForm);
|
||||||
|
},
|
||||||
|
|
||||||
|
activateText: function (value) {
|
||||||
|
'use strict';
|
||||||
|
this.element.html(value);
|
||||||
|
if (this.isPlaceHolder()) {
|
||||||
|
this.element.html(this.placeHolder);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Helper Functions ////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
initOptions: function () {
|
||||||
|
// Try parent supplied info
|
||||||
|
'use strict';
|
||||||
|
var self = this;
|
||||||
|
self.element.parents().each(function () {
|
||||||
|
var $parent = jQuery(this);
|
||||||
|
self.url = self.url || $parent.data("bipUrl");
|
||||||
|
self.activator = self.activator || $parent.data("bipActivator");
|
||||||
|
self.okButton = self.okButton || $parent.data("bipOkButton");
|
||||||
|
self.okButtonClass = self.okButtonClass || $parent.data("bipOkButtonClass");
|
||||||
|
self.cancelButton = self.cancelButton || $parent.data("bipCancelButton");
|
||||||
|
self.cancelButtonClass = self.cancelButtonClass || $parent.data("bipCancelButtonClass");
|
||||||
|
self.skipBlur = self.skipBlur || $parent.data("bipSkipBlur");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load own attributes (overrides all others)
|
||||||
|
self.url = self.element.data("bipUrl") || self.url || document.location.pathname;
|
||||||
|
self.collection = self.element.data("bipCollection") || self.collection;
|
||||||
|
self.formType = self.element.data("bipType") || "input";
|
||||||
|
self.objectName = self.element.data("bipObject") || self.objectName;
|
||||||
|
self.attributeName = self.element.data("bipAttribute") || self.attributeName;
|
||||||
|
self.activator = self.element.data("bipActivator") || self.element;
|
||||||
|
self.okButton = self.element.data("bipOkButton") || self.okButton;
|
||||||
|
self.okButtonClass = self.element.data("bipOkButtonClass") || self.okButtonClass || BestInPlaceEditor.defaults.okButtonClass;
|
||||||
|
self.cancelButton = self.element.data("bipCancelButton") || self.cancelButton;
|
||||||
|
self.cancelButtonClass = self.element.data("bipCancelButtonClass") || self.cancelButtonClass || BestInPlaceEditor.defaults.cancelButtonClass;
|
||||||
|
self.skipBlur = self.element.data("bipSkipBlur") || self.skipBlur || BestInPlaceEditor.defaults.skipBlur;
|
||||||
|
self.isNewObject = self.element.data("bipNewObject");
|
||||||
|
self.dataExtraPayload = self.element.data("bipExtraPayload");
|
||||||
|
|
||||||
|
// Fix for default values of 0
|
||||||
|
if (self.element.data("bipPlaceholder") == null) {
|
||||||
|
self.placeHolder = BestInPlaceEditor.defaults.locales[''].placeHolder;
|
||||||
|
} else {
|
||||||
|
self.placeHolder = self.element.data("bipPlaceholder");
|
||||||
|
}
|
||||||
|
|
||||||
|
self.inner_class = self.element.data("bipInnerClass");
|
||||||
|
self.html_attrs = self.element.data("bipHtmlAttrs");
|
||||||
|
self.original_content = self.element.data("bipOriginalContent") || self.original_content;
|
||||||
|
|
||||||
|
// if set the input won't be satinized
|
||||||
|
self.display_raw = self.element.data("bip-raw");
|
||||||
|
|
||||||
|
self.useConfirm = self.element.data("bip-confirm");
|
||||||
|
|
||||||
|
if (self.formType === "select" || self.formType === "checkbox") {
|
||||||
|
self.values = self.collection;
|
||||||
|
self.collectionValue = self.element.data("bipValue") || self.collectionValue;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
bindForm: function () {
|
||||||
|
'use strict';
|
||||||
|
this.activateForm = BestInPlaceEditor.forms[this.formType].activateForm;
|
||||||
|
this.getValue = BestInPlaceEditor.forms[this.formType].getValue;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
initPlaceHolder: function () {
|
||||||
|
'use strict';
|
||||||
|
// TODO add placeholder for select and checkbox
|
||||||
|
if (this.element.html() === "") {
|
||||||
|
this.element.addClass('bip-placeholder');
|
||||||
|
this.element.html(this.placeHolder);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
isPlaceHolder: function () {
|
||||||
|
'use strict';
|
||||||
|
// TODO: It only work when form is deactivated.
|
||||||
|
// Condition will fail when form is activated
|
||||||
|
return this.element.html() === "" || this.element.html() === this.placeHolder;
|
||||||
|
},
|
||||||
|
|
||||||
|
getValue: function () {
|
||||||
|
'use strict';
|
||||||
|
alert(BestInPlaceEditor.defaults.locales[''].uninitializedForm);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Trim and Strips HTML from text
|
||||||
|
sanitizeValue: function (s) {
|
||||||
|
'use strict';
|
||||||
|
return jQuery.trim(s);
|
||||||
|
},
|
||||||
|
|
||||||
|
requestMethod: function() {
|
||||||
|
'use strict';
|
||||||
|
return this.isNewObject ? 'post' : BestInPlaceEditor.defaults.ajaxMethod;
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Generate the data sent in the POST request */
|
||||||
|
requestData: function () {
|
||||||
|
'use strict';
|
||||||
|
// To prevent xss attacks, a csrf token must be defined as a meta attribute
|
||||||
|
var csrf_token = jQuery('meta[name=csrf-token]').attr('content'),
|
||||||
|
csrf_param = jQuery('meta[name=csrf-param]').attr('content');
|
||||||
|
|
||||||
|
var data = {}
|
||||||
|
data['_method'] = this.requestMethod()
|
||||||
|
|
||||||
|
data[this.objectName] = this.dataExtraPayload || {}
|
||||||
|
|
||||||
|
data[this.objectName][this.attributeName] = this.getValue()
|
||||||
|
|
||||||
|
if (csrf_param !== undefined && csrf_token !== undefined) {
|
||||||
|
data[csrf_param] = csrf_token
|
||||||
|
}
|
||||||
|
return jQuery.param(data);
|
||||||
|
},
|
||||||
|
|
||||||
|
ajax: function (options) {
|
||||||
|
'use strict';
|
||||||
|
options.url = this.url;
|
||||||
|
options.beforeSend = function (xhr) {
|
||||||
|
xhr.setRequestHeader("Accept", "application/json");
|
||||||
|
};
|
||||||
|
return jQuery.ajax(options);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Handlers ////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
loadSuccessCallback: function (data, status, xhr) {
|
||||||
|
'use strict';
|
||||||
|
data = jQuery.trim(data);
|
||||||
|
//Update original content with current text.
|
||||||
|
if (this.display_raw) {
|
||||||
|
this.original_content = this.element.html();
|
||||||
|
} else {
|
||||||
|
this.original_content = this.element.text();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data && data !== "") {
|
||||||
|
var response = jQuery.parseJSON(data);
|
||||||
|
if (response !== null && response.hasOwnProperty("display_as")) {
|
||||||
|
this.element.data('bip-original-content', this.element.text());
|
||||||
|
this.element.html(response.display_as);
|
||||||
|
}
|
||||||
|
if (this.isNewObject && response && response[this.objectName]) {
|
||||||
|
if (response[this.objectName]["id"]) {
|
||||||
|
this.isNewObject = false
|
||||||
|
this.url += "/" + response[this.objectName]["id"] // in REST a POST /thing url should become PUT /thing/123
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.element.toggleClass('bip-placeholder', this.isPlaceHolder());
|
||||||
|
|
||||||
|
this.element.trigger(jQuery.Event("best_in_place:success"), [data, status, xhr]);
|
||||||
|
this.element.trigger(jQuery.Event("ajax:success"), [data, status, xhr]);
|
||||||
|
|
||||||
|
// Binding back after being clicked
|
||||||
|
jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
|
||||||
|
this.element.trigger(jQuery.Event("best_in_place:deactivate"));
|
||||||
|
|
||||||
|
if (this.collectionValue !== null && this.formType === "select") {
|
||||||
|
this.collectionValue = this.previousCollectionValue;
|
||||||
|
this.previousCollectionValue = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
loadErrorCallback: function (request, error) {
|
||||||
|
'use strict';
|
||||||
|
this.activateText(this.oldValue);
|
||||||
|
|
||||||
|
this.element.trigger(jQuery.Event("best_in_place:error"), [request, error]);
|
||||||
|
this.element.trigger(jQuery.Event("ajax:error"), request, error);
|
||||||
|
|
||||||
|
// Binding back after being clicked
|
||||||
|
jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
|
||||||
|
this.element.trigger(jQuery.Event("best_in_place:deactivate"));
|
||||||
|
},
|
||||||
|
|
||||||
|
clickHandler: function (event) {
|
||||||
|
'use strict';
|
||||||
|
event.preventDefault();
|
||||||
|
event.data.editor.activate();
|
||||||
|
},
|
||||||
|
|
||||||
|
setHtmlAttributes: function () {
|
||||||
|
'use strict';
|
||||||
|
var formField = this.element.find(this.formType);
|
||||||
|
|
||||||
|
if (this.html_attrs) {
|
||||||
|
var attrs = this.html_attrs;
|
||||||
|
$.each(attrs, function (key, val) {
|
||||||
|
formField.attr(key, val);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
placeButtons: function (output, field) {
|
||||||
|
'use strict';
|
||||||
|
if (field.okButton) {
|
||||||
|
output.append(
|
||||||
|
jQuery(document.createElement('input'))
|
||||||
|
.attr('type', 'submit')
|
||||||
|
.attr('class', field.okButtonClass)
|
||||||
|
.attr('value', field.okButton)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (field.cancelButton) {
|
||||||
|
output.append(
|
||||||
|
jQuery(document.createElement('input'))
|
||||||
|
.attr('type', 'button')
|
||||||
|
.attr('class', field.cancelButtonClass)
|
||||||
|
.attr('value', field.cancelButton)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Button cases:
|
||||||
|
// If no buttons, then blur saves, ESC cancels
|
||||||
|
// If just Cancel button, then blur saves, ESC or clicking Cancel cancels (careful of blur event!)
|
||||||
|
// If just OK button, then clicking OK saves (careful of blur event!), ESC or blur cancels
|
||||||
|
// If both buttons, then clicking OK saves, ESC or clicking Cancel or blur cancels
|
||||||
|
BestInPlaceEditor.forms = {
|
||||||
|
"input": {
|
||||||
|
activateForm: function () {
|
||||||
|
'use strict';
|
||||||
|
var output = jQuery(document.createElement('form'))
|
||||||
|
.addClass('form_in_place')
|
||||||
|
.attr('action', 'javascript:void(0);')
|
||||||
|
.attr('style', 'display:inline');
|
||||||
|
var input_elt = jQuery(document.createElement('input'))
|
||||||
|
.attr('type', 'text')
|
||||||
|
.attr('name', this.attributeName)
|
||||||
|
.val(this.display_value);
|
||||||
|
|
||||||
|
// Add class to form input
|
||||||
|
if (this.inner_class) {
|
||||||
|
input_elt.addClass(this.inner_class);
|
||||||
|
}
|
||||||
|
|
||||||
|
output.append(input_elt);
|
||||||
|
this.placeButtons(output, this);
|
||||||
|
|
||||||
|
this.element.html(output);
|
||||||
|
this.setHtmlAttributes();
|
||||||
|
|
||||||
|
this.element.find("input[type='text']")[0].select();
|
||||||
|
this.element.find("form").bind('submit', {editor: this}, BestInPlaceEditor.forms.input.submitHandler);
|
||||||
|
if (this.cancelButton) {
|
||||||
|
this.element.find("input[type='button']").bind('click', {editor: this}, BestInPlaceEditor.forms.input.cancelButtonHandler);
|
||||||
|
}
|
||||||
|
if (!this.okButton) {
|
||||||
|
this.element.find("input[type='text']").bind('blur', {editor: this}, BestInPlaceEditor.forms.input.inputBlurHandler);
|
||||||
|
}
|
||||||
|
this.element.find("input[type='text']").bind('keyup', {editor: this}, BestInPlaceEditor.forms.input.keyupHandler);
|
||||||
|
this.blurTimer = null;
|
||||||
|
this.userClicked = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
getValue: function () {
|
||||||
|
'use strict';
|
||||||
|
return this.sanitizeValue(this.element.find("input").val());
|
||||||
|
},
|
||||||
|
|
||||||
|
// When buttons are present, use a timer on the blur event to give precedence to clicks
|
||||||
|
inputBlurHandler: function (event) {
|
||||||
|
'use strict';
|
||||||
|
if (event.data.editor.okButton) {
|
||||||
|
event.data.editor.blurTimer = setTimeout(function () {
|
||||||
|
if (!event.data.editor.userClicked) {
|
||||||
|
event.data.editor.abort();
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
} else {
|
||||||
|
if (event.data.editor.cancelButton) {
|
||||||
|
event.data.editor.blurTimer = setTimeout(function () {
|
||||||
|
if (!event.data.editor.userClicked) {
|
||||||
|
event.data.editor.update();
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
} else {
|
||||||
|
event.data.editor.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
submitHandler: function (event) {
|
||||||
|
'use strict';
|
||||||
|
event.data.editor.userClicked = true;
|
||||||
|
clearTimeout(event.data.editor.blurTimer);
|
||||||
|
event.data.editor.update();
|
||||||
|
},
|
||||||
|
|
||||||
|
cancelButtonHandler: function (event) {
|
||||||
|
'use strict';
|
||||||
|
event.data.editor.userClicked = true;
|
||||||
|
clearTimeout(event.data.editor.blurTimer);
|
||||||
|
event.data.editor.abort();
|
||||||
|
event.stopPropagation(); // Without this, click isn't handled
|
||||||
|
},
|
||||||
|
|
||||||
|
keyupHandler: function (event) {
|
||||||
|
'use strict';
|
||||||
|
if (event.keyCode === 27) {
|
||||||
|
event.data.editor.abort();
|
||||||
|
event.stopImmediatePropagation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"select": {
|
||||||
|
activateForm: function () {
|
||||||
|
'use strict';
|
||||||
|
var output = jQuery(document.createElement('form'))
|
||||||
|
.attr('action', 'javascript:void(0)')
|
||||||
|
.attr('style', 'display:inline'),
|
||||||
|
selected = '',
|
||||||
|
select_elt = jQuery(document.createElement('select'))
|
||||||
|
.attr('class', this.inner_class !== null ? this.inner_class : ''),
|
||||||
|
currentCollectionValue = this.collectionValue,
|
||||||
|
key, value,
|
||||||
|
a = this.values;
|
||||||
|
|
||||||
|
$.each(a, function(index, arr){
|
||||||
|
key = arr[0];
|
||||||
|
value = arr[1];
|
||||||
|
var option_elt = jQuery(document.createElement('option'))
|
||||||
|
.val(key)
|
||||||
|
.html(value);
|
||||||
|
|
||||||
|
if (currentCollectionValue) {
|
||||||
|
if (String(key) === String(currentCollectionValue)) option_elt.attr('selected', 'selected');
|
||||||
|
}
|
||||||
|
select_elt.append(option_elt);
|
||||||
|
});
|
||||||
|
output.append(select_elt);
|
||||||
|
|
||||||
|
this.element.html(output);
|
||||||
|
this.setHtmlAttributes();
|
||||||
|
this.element.find("select").bind('change', {editor: this}, BestInPlaceEditor.forms.select.blurHandler);
|
||||||
|
this.element.find("select").bind('blur', {editor: this}, BestInPlaceEditor.forms.select.blurHandler);
|
||||||
|
this.element.find("select").bind('keyup', {editor: this}, BestInPlaceEditor.forms.select.keyupHandler);
|
||||||
|
this.element.find("select")[0].focus();
|
||||||
|
|
||||||
|
// automatically click on the select so you
|
||||||
|
// don't have to click twice
|
||||||
|
try {
|
||||||
|
var e = document.createEvent("MouseEvents");
|
||||||
|
e.initMouseEvent("mousedown", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
|
||||||
|
this.element.find("select")[0].dispatchEvent(e);
|
||||||
|
}
|
||||||
|
catch(e) {
|
||||||
|
// browser doesn't support this, e.g. IE8
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getValue: function () {
|
||||||
|
'use strict';
|
||||||
|
return this.sanitizeValue(this.element.find("select").val());
|
||||||
|
},
|
||||||
|
|
||||||
|
blurHandler: function (event) {
|
||||||
|
'use strict';
|
||||||
|
event.data.editor.update();
|
||||||
|
},
|
||||||
|
|
||||||
|
keyupHandler: function (event) {
|
||||||
|
'use strict';
|
||||||
|
if (event.keyCode === 27) {
|
||||||
|
event.data.editor.abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"checkbox": {
|
||||||
|
activateForm: function () {
|
||||||
|
'use strict';
|
||||||
|
this.collectionValue = !this.getValue();
|
||||||
|
this.setHtmlAttributes();
|
||||||
|
this.update();
|
||||||
|
},
|
||||||
|
|
||||||
|
getValue: function () {
|
||||||
|
'use strict';
|
||||||
|
return this.collectionValue;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"textarea": {
|
||||||
|
activateForm: function () {
|
||||||
|
'use strict';
|
||||||
|
// grab width and height of text
|
||||||
|
var width = this.element.css('width');
|
||||||
|
var height = this.element.css('height');
|
||||||
|
|
||||||
|
// construct form
|
||||||
|
var output = jQuery(document.createElement('form'))
|
||||||
|
.addClass('form_in_place')
|
||||||
|
.attr('action', 'javascript:void(0);')
|
||||||
|
.attr('style', 'display:inline');
|
||||||
|
var textarea_elt = jQuery(document.createElement('textarea'))
|
||||||
|
.attr('name', this.attributeName)
|
||||||
|
.val(this.sanitizeValue(this.display_value));
|
||||||
|
|
||||||
|
if (this.inner_class !== null) {
|
||||||
|
textarea_elt.addClass(this.inner_class);
|
||||||
|
}
|
||||||
|
|
||||||
|
output.append(textarea_elt);
|
||||||
|
|
||||||
|
this.placeButtons(output, this);
|
||||||
|
|
||||||
|
this.element.html(output);
|
||||||
|
this.setHtmlAttributes();
|
||||||
|
|
||||||
|
// set width and height of textarea
|
||||||
|
jQuery(this.element.find("textarea")[0]).css({'min-width': width, 'min-height': height});
|
||||||
|
jQuery(this.element.find("textarea")[0]).autosize();
|
||||||
|
|
||||||
|
this.element.find("textarea")[0].focus();
|
||||||
|
this.element.find("form").bind('submit', {editor: this}, BestInPlaceEditor.forms.textarea.submitHandler);
|
||||||
|
|
||||||
|
if (this.cancelButton) {
|
||||||
|
this.element.find("input[type='button']").bind('click', {editor: this}, BestInPlaceEditor.forms.textarea.cancelButtonHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.skipBlur) {
|
||||||
|
this.element.find("textarea").bind('blur', {editor: this}, BestInPlaceEditor.forms.textarea.blurHandler);
|
||||||
|
}
|
||||||
|
this.element.find("textarea").bind('keyup', {editor: this}, BestInPlaceEditor.forms.textarea.keyupHandler);
|
||||||
|
this.blurTimer = null;
|
||||||
|
this.userClicked = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
getValue: function () {
|
||||||
|
'use strict';
|
||||||
|
return this.sanitizeValue(this.element.find("textarea").val());
|
||||||
|
},
|
||||||
|
|
||||||
|
// When buttons are present, use a timer on the blur event to give precedence to clicks
|
||||||
|
blurHandler: function (event) {
|
||||||
|
'use strict';
|
||||||
|
if (event.data.editor.okButton) {
|
||||||
|
event.data.editor.blurTimer = setTimeout(function () {
|
||||||
|
if (!event.data.editor.userClicked) {
|
||||||
|
event.data.editor.abortIfConfirm();
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
} else {
|
||||||
|
if (event.data.editor.cancelButton) {
|
||||||
|
event.data.editor.blurTimer = setTimeout(function () {
|
||||||
|
if (!event.data.editor.userClicked) {
|
||||||
|
event.data.editor.update();
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
} else {
|
||||||
|
event.data.editor.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
submitHandler: function (event) {
|
||||||
|
'use strict';
|
||||||
|
event.data.editor.userClicked = true;
|
||||||
|
clearTimeout(event.data.editor.blurTimer);
|
||||||
|
event.data.editor.update();
|
||||||
|
},
|
||||||
|
|
||||||
|
cancelButtonHandler: function (event) {
|
||||||
|
'use strict';
|
||||||
|
event.data.editor.userClicked = true;
|
||||||
|
clearTimeout(event.data.editor.blurTimer);
|
||||||
|
event.data.editor.abortIfConfirm();
|
||||||
|
event.stopPropagation(); // Without this, click isn't handled
|
||||||
|
},
|
||||||
|
|
||||||
|
keyupHandler: function (event) {
|
||||||
|
'use strict';
|
||||||
|
if (event.keyCode === 27) {
|
||||||
|
event.data.editor.abortIfConfirm();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
BestInPlaceEditor.defaults = {
|
||||||
|
locales: {},
|
||||||
|
ajaxMethod: "put", //TODO Change to patch when support to 3.2 is dropped
|
||||||
|
ajaxDataType: 'text',
|
||||||
|
okButtonClass: '',
|
||||||
|
cancelButtonClass: '',
|
||||||
|
skipBlur: false
|
||||||
|
};
|
||||||
|
|
||||||
|
// Default locale
|
||||||
|
BestInPlaceEditor.defaults.locales[''] = {
|
||||||
|
confirmMessage: "Are you sure you want to discard your changes?",
|
||||||
|
uninitializedForm: "The form was not properly initialized. getValue is unbound",
|
||||||
|
placeHolder: '-'
|
||||||
|
};
|
||||||
|
|
||||||
|
jQuery.fn.best_in_place = function () {
|
||||||
|
'use strict';
|
||||||
|
function setBestInPlace(element) {
|
||||||
|
if (!element.data('bestInPlaceEditor')) {
|
||||||
|
element.data('bestInPlaceEditor', new BestInPlaceEditor(element));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jQuery(this.context).delegate(this.selector, 'click', function () {
|
||||||
|
var el = jQuery(this);
|
||||||
|
if (setBestInPlace(el)) {
|
||||||
|
el.click();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.each(function () {
|
||||||
|
setBestInPlace(jQuery(this));
|
||||||
|
});
|
||||||
|
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,780 +0,0 @@
|
||||||
/*
|
|
||||||
BestInPlace (for jQuery)
|
|
||||||
version: 0.1.0 (01/01/2011)
|
|
||||||
@requires jQuery >= v1.4
|
|
||||||
@requires jQuery.purr to display pop-up windows
|
|
||||||
|
|
||||||
By Bernat Farrero based on the work of Jan Varwig.
|
|
||||||
Examples at http://bernatfarrero.com
|
|
||||||
|
|
||||||
Licensed under the MIT:
|
|
||||||
http://www.opensource.org/licenses/mit-license.php
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
|
|
||||||
Attention.
|
|
||||||
The format of the JSON object given to the select inputs is the following:
|
|
||||||
[["key", "value"],["key", "value"]]
|
|
||||||
The format of the JSON object given to the checkbox inputs is the following:
|
|
||||||
["falseValue", "trueValue"]
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
function BestInPlaceEditor(e) {
|
|
||||||
this.element = e;
|
|
||||||
this.initOptions();
|
|
||||||
this.bindForm();
|
|
||||||
this.initNil();
|
|
||||||
jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
BestInPlaceEditor.prototype = {
|
|
||||||
// Public Interface Functions //////////////////////////////////////////////
|
|
||||||
|
|
||||||
activate : function() {
|
|
||||||
var to_display = "";
|
|
||||||
if (this.isNil()) {
|
|
||||||
to_display = "";
|
|
||||||
}
|
|
||||||
else if (this.original_content) {
|
|
||||||
to_display = this.original_content;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (this.sanitize) {
|
|
||||||
to_display = this.element.text();
|
|
||||||
} else {
|
|
||||||
to_display = this.element.html();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.oldValue = this.isNil() ? "" : this.element.html();
|
|
||||||
this.display_value = to_display;
|
|
||||||
jQuery(this.activator).unbind("click", this.clickHandler);
|
|
||||||
this.activateForm();
|
|
||||||
this.element.trigger(jQuery.Event("best_in_place:activate"));
|
|
||||||
},
|
|
||||||
|
|
||||||
abort : function() {
|
|
||||||
this.activateText(this.oldValue);
|
|
||||||
jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
|
|
||||||
this.element.trigger(jQuery.Event("best_in_place:abort"));
|
|
||||||
this.element.trigger(jQuery.Event("best_in_place:deactivate"));
|
|
||||||
},
|
|
||||||
|
|
||||||
abortIfConfirm : function () {
|
|
||||||
if (!this.useConfirm) {
|
|
||||||
this.abort();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (confirm("Are you sure you want to discard your changes?")) {
|
|
||||||
this.abort();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
update : function() {
|
|
||||||
var editor = this;
|
|
||||||
if (this.formType in {"input":1, "textarea":1} && this.getValue() == this.oldValue)
|
|
||||||
{ // Avoid request if no change is made
|
|
||||||
this.abort();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
editor.ajax({
|
|
||||||
"type" : "post",
|
|
||||||
"dataType" : "text",
|
|
||||||
"data" : editor.requestData(),
|
|
||||||
"success" : function(data){ editor.loadSuccessCallback(data); },
|
|
||||||
"error" : function(request, error){ editor.loadErrorCallback(request, error); }
|
|
||||||
});
|
|
||||||
if (this.formType == "select") {
|
|
||||||
var value = this.getValue();
|
|
||||||
this.previousCollectionValue = value;
|
|
||||||
|
|
||||||
jQuery.each(this.values, function(i, v) {
|
|
||||||
if (value == v[0]) {
|
|
||||||
editor.element.html(v[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else if (this.formType == "checkbox") {
|
|
||||||
editor.element.html(this.getValue() ? this.values[1] : this.values[0]);
|
|
||||||
} else {
|
|
||||||
if (this.getValue() !== "") {
|
|
||||||
editor.element.text(this.getValue());
|
|
||||||
} else {
|
|
||||||
editor.element.html(this.nil);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
editor.element.trigger(jQuery.Event("best_in_place:update"));
|
|
||||||
},
|
|
||||||
|
|
||||||
activateForm : function() {
|
|
||||||
alert("The form was not properly initialized. activateForm is unbound");
|
|
||||||
},
|
|
||||||
|
|
||||||
activateText : function(value){
|
|
||||||
this.element.html(value);
|
|
||||||
if(this.isNil()) this.element.html(this.nil);
|
|
||||||
},
|
|
||||||
|
|
||||||
// Helper Functions ////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
initOptions : function() {
|
|
||||||
// Try parent supplied info
|
|
||||||
var self = this;
|
|
||||||
self.element.parents().each(function(){
|
|
||||||
$parent = jQuery(this);
|
|
||||||
self.url = self.url || $parent.attr("data-url");
|
|
||||||
self.collection = self.collection || $parent.attr("data-collection");
|
|
||||||
self.formType = self.formType || $parent.attr("data-type");
|
|
||||||
self.objectName = self.objectName || $parent.attr("data-object");
|
|
||||||
self.attributeName = self.attributeName || $parent.attr("data-attribute");
|
|
||||||
self.activator = self.activator || $parent.attr("data-activator");
|
|
||||||
self.okButton = self.okButton || $parent.attr("data-ok-button");
|
|
||||||
self.okButtonClass = self.okButtonClass || $parent.attr("data-ok-button-class");
|
|
||||||
self.cancelButton = self.cancelButton || $parent.attr("data-cancel-button");
|
|
||||||
self.cancelButtonClass = self.cancelButtonClass || $parent.attr("data-cancel-button-class");
|
|
||||||
self.nil = self.nil || $parent.attr("data-nil");
|
|
||||||
self.inner_class = self.inner_class || $parent.attr("data-inner-class");
|
|
||||||
self.html_attrs = self.html_attrs || $parent.attr("data-html-attrs");
|
|
||||||
self.original_content = self.original_content || $parent.attr("data-original-content");
|
|
||||||
self.collectionValue = self.collectionValue || $parent.attr("data-value");
|
|
||||||
});
|
|
||||||
|
|
||||||
// Try Rails-id based if parents did not explicitly supply something
|
|
||||||
self.element.parents().each(function(){
|
|
||||||
var res = this.id.match(/^(\w+)_(\d+)$/i);
|
|
||||||
if (res) {
|
|
||||||
self.objectName = self.objectName || res[1];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Load own attributes (overrides all others)
|
|
||||||
self.url = self.element.attr("data-url") || self.url || document.location.pathname;
|
|
||||||
self.collection = self.element.attr("data-collection") || self.collection;
|
|
||||||
self.formType = self.element.attr("data-type") || self.formtype || "input";
|
|
||||||
self.objectName = self.element.attr("data-object") || self.objectName;
|
|
||||||
self.attributeName = self.element.attr("data-attribute") || self.attributeName;
|
|
||||||
self.activator = self.element.attr("data-activator") || self.element;
|
|
||||||
self.okButton = self.element.attr("data-ok-button") || self.okButton;
|
|
||||||
self.okButtonClass = self.element.attr("data-ok-button-class") || self.okButtonClass || "";
|
|
||||||
self.cancelButton = self.element.attr("data-cancel-button") || self.cancelButton;
|
|
||||||
self.cancelButtonClass = self.element.attr("data-cancel-button-class") || self.cancelButtonClass || "";
|
|
||||||
self.nil = self.element.attr("data-nil") || self.nil || "—";
|
|
||||||
self.inner_class = self.element.attr("data-inner-class") || self.inner_class || null;
|
|
||||||
self.html_attrs = self.element.attr("data-html-attrs") || self.html_attrs;
|
|
||||||
self.original_content = self.element.attr("data-original-content") || self.original_content;
|
|
||||||
self.collectionValue = self.element.attr("data-value") || self.collectionValue;
|
|
||||||
|
|
||||||
if (!self.element.attr("data-sanitize")) {
|
|
||||||
self.sanitize = true;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
self.sanitize = (self.element.attr("data-sanitize") == "true");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!self.element.attr("data-use-confirm")) {
|
|
||||||
self.useConfirm = true;
|
|
||||||
} else {
|
|
||||||
self.useConfirm = (self.element.attr("data-use-confirm") != "false");
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((self.formType == "select" || self.formType == "checkbox") && self.collection !== null)
|
|
||||||
{
|
|
||||||
self.values = jQuery.parseJSON(self.collection);
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
bindForm : function() {
|
|
||||||
this.activateForm = BestInPlaceEditor.forms[this.formType].activateForm;
|
|
||||||
this.getValue = BestInPlaceEditor.forms[this.formType].getValue;
|
|
||||||
},
|
|
||||||
|
|
||||||
initNil: function() {
|
|
||||||
if (this.element.html() === "")
|
|
||||||
{
|
|
||||||
this.element.html(this.nil);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
isNil: function() {
|
|
||||||
// TODO: It only work when form is deactivated.
|
|
||||||
// Condition will fail when form is activated
|
|
||||||
return this.element.html() === "" || this.element.html() === this.nil;
|
|
||||||
},
|
|
||||||
|
|
||||||
getValue : function() {
|
|
||||||
alert("The form was not properly initialized. getValue is unbound");
|
|
||||||
},
|
|
||||||
|
|
||||||
// Trim and Strips HTML from text
|
|
||||||
sanitizeValue : function(s) {
|
|
||||||
return jQuery.trim(s);
|
|
||||||
},
|
|
||||||
|
|
||||||
/* Generate the data sent in the POST request */
|
|
||||||
requestData : function() {
|
|
||||||
// To prevent xss attacks, a csrf token must be defined as a meta attribute
|
|
||||||
csrf_token = jQuery('meta[name=csrf-token]').attr('content');
|
|
||||||
csrf_param = jQuery('meta[name=csrf-param]').attr('content');
|
|
||||||
|
|
||||||
var data = "_method=put";
|
|
||||||
data += "&" + this.objectName + '[' + this.attributeName + ']=' + encodeURIComponent(this.getValue());
|
|
||||||
|
|
||||||
if (csrf_param !== undefined && csrf_token !== undefined) {
|
|
||||||
data += "&" + csrf_param + "=" + encodeURIComponent(csrf_token);
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
|
|
||||||
ajax : function(options) {
|
|
||||||
options.url = this.url;
|
|
||||||
options.beforeSend = function(xhr){ xhr.setRequestHeader("Accept", "application/json"); };
|
|
||||||
return jQuery.ajax(options);
|
|
||||||
},
|
|
||||||
|
|
||||||
// Handlers ////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
loadSuccessCallback : function(data) {
|
|
||||||
data = jQuery.trim(data);
|
|
||||||
|
|
||||||
if(data && data!=""){
|
|
||||||
var response = jQuery.parseJSON(jQuery.trim(data));
|
|
||||||
if (response !== null && response.hasOwnProperty("display_as")) {
|
|
||||||
this.element.attr("data-original-content", this.element.text());
|
|
||||||
this.original_content = this.element.text();
|
|
||||||
this.element.html(response["display_as"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.element.trigger(jQuery.Event("best_in_place:success"), data);
|
|
||||||
this.element.trigger(jQuery.Event("ajax:success"), data);
|
|
||||||
} else {
|
|
||||||
this.element.trigger(jQuery.Event("best_in_place:success"));
|
|
||||||
this.element.trigger(jQuery.Event("ajax:success"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Binding back after being clicked
|
|
||||||
jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
|
|
||||||
this.element.trigger(jQuery.Event("best_in_place:deactivate"));
|
|
||||||
|
|
||||||
if (this.collectionValue !== null && this.formType == "select") {
|
|
||||||
this.collectionValue = this.previousCollectionValue;
|
|
||||||
this.previousCollectionValue = null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
loadErrorCallback : function(request, error) {
|
|
||||||
this.activateText(this.oldValue);
|
|
||||||
|
|
||||||
this.element.trigger(jQuery.Event("best_in_place:error"), [request, error]);
|
|
||||||
this.element.trigger(jQuery.Event("ajax:error"), request, error);
|
|
||||||
|
|
||||||
// Binding back after being clicked
|
|
||||||
jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
|
|
||||||
this.element.trigger(jQuery.Event("best_in_place:deactivate"));
|
|
||||||
},
|
|
||||||
|
|
||||||
clickHandler : function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.data.editor.activate();
|
|
||||||
},
|
|
||||||
|
|
||||||
setHtmlAttributes : function() {
|
|
||||||
var formField = this.element.find(this.formType);
|
|
||||||
|
|
||||||
if(this.html_attrs){
|
|
||||||
var attrs = jQuery.parseJSON(this.html_attrs);
|
|
||||||
for(var key in attrs){
|
|
||||||
formField.attr(key, attrs[key]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// Button cases:
|
|
||||||
// If no buttons, then blur saves, ESC cancels
|
|
||||||
// If just Cancel button, then blur saves, ESC or clicking Cancel cancels (careful of blur event!)
|
|
||||||
// If just OK button, then clicking OK saves (careful of blur event!), ESC or blur cancels
|
|
||||||
// If both buttons, then clicking OK saves, ESC or clicking Cancel or blur cancels
|
|
||||||
BestInPlaceEditor.forms = {
|
|
||||||
"input" : {
|
|
||||||
activateForm : function() {
|
|
||||||
var output = jQuery(document.createElement('form'))
|
|
||||||
.addClass('form_in_place')
|
|
||||||
.attr('action', 'javascript:void(0);')
|
|
||||||
.attr('style', 'display:inline');
|
|
||||||
var input_elt = jQuery(document.createElement('input'))
|
|
||||||
.attr('type', 'text')
|
|
||||||
.attr('name', this.attributeName)
|
|
||||||
.val(this.display_value);
|
|
||||||
if(this.inner_class !== null) {
|
|
||||||
input_elt.addClass(this.inner_class);
|
|
||||||
}
|
|
||||||
output.append(input_elt);
|
|
||||||
if(this.okButton) {
|
|
||||||
output.append(
|
|
||||||
jQuery(document.createElement('input'))
|
|
||||||
.attr('type', 'submit')
|
|
||||||
.attr('class', this.okButtonClass)
|
|
||||||
.attr('value', this.okButton)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if(this.cancelButton) {
|
|
||||||
output.append(
|
|
||||||
jQuery(document.createElement('input'))
|
|
||||||
.attr('type', 'button')
|
|
||||||
.attr('class', this.cancelButtonClass)
|
|
||||||
.attr('value', this.cancelButton)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.element.html(output);
|
|
||||||
this.setHtmlAttributes();
|
|
||||||
// START METAMAPS CODE
|
|
||||||
//this.element.find("input[type='text']")[0].select();
|
|
||||||
this.element.find("input[type='text']")[0].focus();
|
|
||||||
// END METAMAPS CODE
|
|
||||||
this.element.find("form").bind('submit', {editor: this}, BestInPlaceEditor.forms.input.submitHandler);
|
|
||||||
if (this.cancelButton) {
|
|
||||||
this.element.find("input[type='button']").bind('click', {editor: this}, BestInPlaceEditor.forms.input.cancelButtonHandler);
|
|
||||||
}
|
|
||||||
this.element.find("input[type='text']").bind('blur', {editor: this}, BestInPlaceEditor.forms.input.inputBlurHandler);
|
|
||||||
// START METAMAPS CODE
|
|
||||||
this.element.find("input[type='text']").bind('keydown', {editor: this}, BestInPlaceEditor.forms.input.keydownHandler);
|
|
||||||
// END METAMAPS CODE
|
|
||||||
this.element.find("input[type='text']").bind('keyup', {editor: this}, BestInPlaceEditor.forms.input.keyupHandler);
|
|
||||||
this.blurTimer = null;
|
|
||||||
this.userClicked = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
getValue : function() {
|
|
||||||
return this.sanitizeValue(this.element.find("input").val());
|
|
||||||
},
|
|
||||||
|
|
||||||
// When buttons are present, use a timer on the blur event to give precedence to clicks
|
|
||||||
inputBlurHandler : function(event) {
|
|
||||||
if (event.data.editor.okButton) {
|
|
||||||
event.data.editor.blurTimer = setTimeout(function () {
|
|
||||||
if (!event.data.editor.userClicked) {
|
|
||||||
event.data.editor.abort();
|
|
||||||
}
|
|
||||||
}, 500);
|
|
||||||
} else {
|
|
||||||
if (event.data.editor.cancelButton) {
|
|
||||||
event.data.editor.blurTimer = setTimeout(function () {
|
|
||||||
if (!event.data.editor.userClicked) {
|
|
||||||
event.data.editor.update();
|
|
||||||
}
|
|
||||||
}, 500);
|
|
||||||
} else {
|
|
||||||
event.data.editor.update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
submitHandler : function(event) {
|
|
||||||
event.data.editor.userClicked = true;
|
|
||||||
clearTimeout(event.data.editor.blurTimer);
|
|
||||||
event.data.editor.update();
|
|
||||||
},
|
|
||||||
|
|
||||||
cancelButtonHandler : function(event) {
|
|
||||||
event.data.editor.userClicked = true;
|
|
||||||
clearTimeout(event.data.editor.blurTimer);
|
|
||||||
event.data.editor.abort();
|
|
||||||
event.stopPropagation(); // Without this, click isn't handled
|
|
||||||
},
|
|
||||||
|
|
||||||
keyupHandler : function(event) {
|
|
||||||
if (event.keyCode == 27) {
|
|
||||||
event.data.editor.abort();
|
|
||||||
}
|
|
||||||
// START METAMAPS CODE
|
|
||||||
else if (event.keyCode == 13 && !event.shiftKey) {
|
|
||||||
event.data.editor.update();
|
|
||||||
}
|
|
||||||
// END METAMAPS CODE
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"date" : {
|
|
||||||
activateForm : function() {
|
|
||||||
var that = this,
|
|
||||||
output = jQuery(document.createElement('form'))
|
|
||||||
.addClass('form_in_place')
|
|
||||||
.attr('action', 'javascript:void(0);')
|
|
||||||
.attr('style', 'display:inline'),
|
|
||||||
input_elt = jQuery(document.createElement('input'))
|
|
||||||
.attr('type', 'text')
|
|
||||||
.attr('name', this.attributeName)
|
|
||||||
.attr('value', this.sanitizeValue(this.display_value));
|
|
||||||
if(this.inner_class !== null) {
|
|
||||||
input_elt.addClass(this.inner_class);
|
|
||||||
}
|
|
||||||
output.append(input_elt)
|
|
||||||
|
|
||||||
this.element.html(output);
|
|
||||||
this.setHtmlAttributes();
|
|
||||||
this.element.find('input')[0].select();
|
|
||||||
this.element.find("form").bind('submit', {editor: this}, BestInPlaceEditor.forms.input.submitHandler);
|
|
||||||
this.element.find("input").bind('keyup', {editor: this}, BestInPlaceEditor.forms.input.keyupHandler);
|
|
||||||
|
|
||||||
this.element.find('input')
|
|
||||||
.datepicker({
|
|
||||||
onClose: function() {
|
|
||||||
that.update();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.datepicker('show');
|
|
||||||
},
|
|
||||||
|
|
||||||
getValue : function() {
|
|
||||||
return this.sanitizeValue(this.element.find("input").val());
|
|
||||||
},
|
|
||||||
|
|
||||||
submitHandler : function(event) {
|
|
||||||
event.data.editor.update();
|
|
||||||
},
|
|
||||||
|
|
||||||
// START METAMAPS CODE
|
|
||||||
keydownHandler : function(event) {
|
|
||||||
if (event.keyCode == 13 && !event.shiftKey) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// END METAMAPS CODE
|
|
||||||
|
|
||||||
keyupHandler : function(event) {
|
|
||||||
if (event.keyCode == 27) {
|
|
||||||
event.data.editor.abort();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"select" : {
|
|
||||||
activateForm : function() {
|
|
||||||
var output = jQuery(document.createElement('form'))
|
|
||||||
.attr('action', 'javascript:void(0)')
|
|
||||||
.attr('style', 'display:inline');
|
|
||||||
selected = '',
|
|
||||||
oldValue = this.oldValue,
|
|
||||||
select_elt = jQuery(document.createElement('select'))
|
|
||||||
.attr('class', this.inned_class !== null ? this.inner_class : '' ),
|
|
||||||
currentCollectionValue = this.collectionValue;
|
|
||||||
|
|
||||||
jQuery.each(this.values, function (index, value) {
|
|
||||||
var option_elt = jQuery(document.createElement('option'))
|
|
||||||
// .attr('value', value[0])
|
|
||||||
.val(value[0])
|
|
||||||
.html(value[1]);
|
|
||||||
if(value[0] == currentCollectionValue) {
|
|
||||||
option_elt.attr('selected', 'selected');
|
|
||||||
}
|
|
||||||
select_elt.append(option_elt);
|
|
||||||
});
|
|
||||||
output.append(select_elt);
|
|
||||||
|
|
||||||
this.element.html(output);
|
|
||||||
this.setHtmlAttributes();
|
|
||||||
this.element.find("select").bind('change', {editor: this}, BestInPlaceEditor.forms.select.blurHandler);
|
|
||||||
this.element.find("select").bind('blur', {editor: this}, BestInPlaceEditor.forms.select.blurHandler);
|
|
||||||
this.element.find("select").bind('keyup', {editor: this}, BestInPlaceEditor.forms.select.keyupHandler);
|
|
||||||
this.element.find("select")[0].focus();
|
|
||||||
},
|
|
||||||
|
|
||||||
getValue : function() {
|
|
||||||
return this.sanitizeValue(this.element.find("select").val());
|
|
||||||
// return this.element.find("select").val();
|
|
||||||
},
|
|
||||||
|
|
||||||
blurHandler : function(event) {
|
|
||||||
event.data.editor.update();
|
|
||||||
},
|
|
||||||
|
|
||||||
keyupHandler : function(event) {
|
|
||||||
if (event.keyCode == 27) event.data.editor.abort();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"checkbox" : {
|
|
||||||
activateForm : function() {
|
|
||||||
this.collectionValue = !this.getValue();
|
|
||||||
this.setHtmlAttributes();
|
|
||||||
this.update();
|
|
||||||
},
|
|
||||||
|
|
||||||
getValue : function() {
|
|
||||||
return this.collectionValue;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"textarea" : {
|
|
||||||
activateForm : function() {
|
|
||||||
// grab width and height of text
|
|
||||||
width = this.element.css('width');
|
|
||||||
height = this.element.css('height');
|
|
||||||
|
|
||||||
// construct form
|
|
||||||
var output = jQuery(document.createElement('form'))
|
|
||||||
.attr('action', 'javascript:void(0)')
|
|
||||||
.attr('style', 'display:inline')
|
|
||||||
.append(jQuery(document.createElement('textarea'))
|
|
||||||
.val(this.sanitizeValue(this.display_value)));
|
|
||||||
if(this.okButton) {
|
|
||||||
output.append(
|
|
||||||
jQuery(document.createElement('input'))
|
|
||||||
.attr('type', 'submit')
|
|
||||||
.attr('value', this.okButton)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if(this.cancelButton) {
|
|
||||||
output.append(
|
|
||||||
jQuery(document.createElement('input'))
|
|
||||||
.attr('type', 'button')
|
|
||||||
.attr('value', this.cancelButton)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.element.html(output);
|
|
||||||
this.setHtmlAttributes();
|
|
||||||
|
|
||||||
// set width and height of textarea
|
|
||||||
jQuery(this.element.find("textarea")[0]).css({ 'min-width': width, 'min-height': height });
|
|
||||||
jQuery(this.element.find("textarea")[0]).elastic();
|
|
||||||
|
|
||||||
this.element.find("textarea")[0].focus();
|
|
||||||
this.element.find("form").bind('submit', {editor: this}, BestInPlaceEditor.forms.textarea.submitHandler);
|
|
||||||
if (this.cancelButton) {
|
|
||||||
this.element.find("input[type='button']").bind('click', {editor: this}, BestInPlaceEditor.forms.textarea.cancelButtonHandler);
|
|
||||||
}
|
|
||||||
this.element.find("textarea").bind('blur', {editor: this}, BestInPlaceEditor.forms.textarea.blurHandler);
|
|
||||||
// START METAMAPS CODE
|
|
||||||
this.element.find("textarea").bind('keydown', {editor: this}, BestInPlaceEditor.forms.textarea.keydownHandler);
|
|
||||||
// END METAMAPS CODE
|
|
||||||
this.element.find("textarea").bind('keyup', {editor: this}, BestInPlaceEditor.forms.textarea.keyupHandler);
|
|
||||||
this.blurTimer = null;
|
|
||||||
this.userClicked = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
getValue : function() {
|
|
||||||
return this.sanitizeValue(this.element.find("textarea").val());
|
|
||||||
},
|
|
||||||
|
|
||||||
// When buttons are present, use a timer on the blur event to give precedence to clicks
|
|
||||||
blurHandler : function(event) {
|
|
||||||
if (event.data.editor.okButton) {
|
|
||||||
event.data.editor.blurTimer = setTimeout(function () {
|
|
||||||
if (!event.data.editor.userClicked) {
|
|
||||||
event.data.editor.abortIfConfirm();
|
|
||||||
}
|
|
||||||
}, 500);
|
|
||||||
} else {
|
|
||||||
if (event.data.editor.cancelButton) {
|
|
||||||
event.data.editor.blurTimer = setTimeout(function () {
|
|
||||||
if (!event.data.editor.userClicked) {
|
|
||||||
event.data.editor.update();
|
|
||||||
}
|
|
||||||
}, 500);
|
|
||||||
} else {
|
|
||||||
event.data.editor.update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
submitHandler : function(event) {
|
|
||||||
event.data.editor.userClicked = true;
|
|
||||||
clearTimeout(event.data.editor.blurTimer);
|
|
||||||
event.data.editor.update();
|
|
||||||
},
|
|
||||||
|
|
||||||
cancelButtonHandler : function(event) {
|
|
||||||
event.data.editor.userClicked = true;
|
|
||||||
clearTimeout(event.data.editor.blurTimer);
|
|
||||||
event.data.editor.abortIfConfirm();
|
|
||||||
event.stopPropagation(); // Without this, click isn't handled
|
|
||||||
},
|
|
||||||
|
|
||||||
// START METAMAPS CODE
|
|
||||||
keydownHandler : function(event) {
|
|
||||||
if (event.keyCode == 13 && !event.shiftKey) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// END METAMAPS CODE
|
|
||||||
|
|
||||||
keyupHandler : function(event) {
|
|
||||||
if (event.keyCode == 27) {
|
|
||||||
event.data.editor.abortIfConfirm();
|
|
||||||
}
|
|
||||||
// START METAMAPS CODE
|
|
||||||
else if (event.keyCode == 13 && !event.shiftKey) {
|
|
||||||
event.data.editor.update();
|
|
||||||
}
|
|
||||||
// END METAMAPS CODE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
jQuery.fn.best_in_place = function() {
|
|
||||||
|
|
||||||
function setBestInPlace(element) {
|
|
||||||
if (!element.data('bestInPlaceEditor')) {
|
|
||||||
element.data('bestInPlaceEditor', new BestInPlaceEditor(element));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
jQuery(this.context).delegate(this.selector, 'click', function () {
|
|
||||||
var el = jQuery(this);
|
|
||||||
if (setBestInPlace(el))
|
|
||||||
el.click();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.each(function () {
|
|
||||||
setBestInPlace(jQuery(this));
|
|
||||||
});
|
|
||||||
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @name Elastic
|
|
||||||
* @descripton Elastic is Jquery plugin that grow and shrink your textareas automaticliy
|
|
||||||
* @version 1.6.5
|
|
||||||
* @requires Jquery 1.2.6+
|
|
||||||
*
|
|
||||||
* @author Jan Jarfalk
|
|
||||||
* @author-email jan.jarfalk@unwrongest.com
|
|
||||||
* @author-website http://www.unwrongest.com
|
|
||||||
*
|
|
||||||
* @licens MIT License - http://www.opensource.org/licenses/mit-license.php
|
|
||||||
*/
|
|
||||||
|
|
||||||
(function(jQuery){
|
|
||||||
if (typeof jQuery.fn.elastic !== 'undefined') return;
|
|
||||||
|
|
||||||
jQuery.fn.extend({
|
|
||||||
elastic: function() {
|
|
||||||
// We will create a div clone of the textarea
|
|
||||||
// by copying these attributes from the textarea to the div.
|
|
||||||
var mimics = [
|
|
||||||
'paddingTop',
|
|
||||||
'paddingRight',
|
|
||||||
'paddingBottom',
|
|
||||||
'paddingLeft',
|
|
||||||
'fontSize',
|
|
||||||
'lineHeight',
|
|
||||||
'fontFamily',
|
|
||||||
'width',
|
|
||||||
'fontWeight'];
|
|
||||||
|
|
||||||
return this.each( function() {
|
|
||||||
|
|
||||||
// Elastic only works on textareas
|
|
||||||
if ( this.type != 'textarea' ) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var $textarea = jQuery(this),
|
|
||||||
$twin = jQuery('<div />').css({'position': 'absolute','display':'none','word-wrap':'break-word'}),
|
|
||||||
lineHeight = parseInt($textarea.css('line-height'),10) || parseInt($textarea.css('font-size'),'10'),
|
|
||||||
minheight = parseInt($textarea.css('height'),10) || lineHeight*3,
|
|
||||||
maxheight = parseInt($textarea.css('max-height'),10) || Number.MAX_VALUE,
|
|
||||||
goalheight = 0,
|
|
||||||
i = 0;
|
|
||||||
|
|
||||||
// Opera returns max-height of -1 if not set
|
|
||||||
if (maxheight < 0) { maxheight = Number.MAX_VALUE; }
|
|
||||||
|
|
||||||
// Append the twin to the DOM
|
|
||||||
// We are going to meassure the height of this, not the textarea.
|
|
||||||
$twin.appendTo($textarea.parent());
|
|
||||||
|
|
||||||
// Copy the essential styles (mimics) from the textarea to the twin
|
|
||||||
i = mimics.length;
|
|
||||||
while(i--){
|
|
||||||
$twin.css(mimics[i].toString(),$textarea.css(mimics[i].toString()));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Sets a given height and overflow state on the textarea
|
|
||||||
function setHeightAndOverflow(height, overflow){
|
|
||||||
curratedHeight = Math.floor(parseInt(height,10));
|
|
||||||
if($textarea.height() != curratedHeight){
|
|
||||||
$textarea.css({'height': curratedHeight + 'px','overflow':overflow});
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// This function will update the height of the textarea if necessary
|
|
||||||
function update() {
|
|
||||||
|
|
||||||
// Get curated content from the textarea.
|
|
||||||
var textareaContent = $textarea.val().replace(/&/g,'&').replace(/ /g, ' ').replace(/<|>/g, '>').replace(/\n/g, '<br />');
|
|
||||||
|
|
||||||
// Compare curated content with curated twin.
|
|
||||||
var twinContent = $twin.html().replace(/<br>/ig,'<br />');
|
|
||||||
|
|
||||||
if(textareaContent+' ' != twinContent){
|
|
||||||
|
|
||||||
// Add an extra white space so new rows are added when you are at the end of a row.
|
|
||||||
$twin.html(textareaContent+' ');
|
|
||||||
|
|
||||||
// Change textarea height if twin plus the height of one line differs more than 3 pixel from textarea height
|
|
||||||
if(Math.abs($twin.height() + lineHeight - $textarea.height()) > 3){
|
|
||||||
|
|
||||||
var goalheight = $twin.height()+lineHeight;
|
|
||||||
if(goalheight >= maxheight) {
|
|
||||||
setHeightAndOverflow(maxheight,'auto');
|
|
||||||
} else if(goalheight <= minheight) {
|
|
||||||
setHeightAndOverflow(minheight,'hidden');
|
|
||||||
} else {
|
|
||||||
setHeightAndOverflow(goalheight,'hidden');
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hide scrollbars
|
|
||||||
$textarea.css({'overflow':'hidden'});
|
|
||||||
|
|
||||||
// Update textarea size on keyup, change, cut and paste
|
|
||||||
$textarea.bind('keyup change cut paste', function(){
|
|
||||||
update();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Compact textarea on blur
|
|
||||||
// Lets animate this....
|
|
||||||
$textarea.bind('blur',function(){
|
|
||||||
if($twin.height() < maxheight){
|
|
||||||
if($twin.height() > minheight) {
|
|
||||||
$textarea.height($twin.height());
|
|
||||||
} else {
|
|
||||||
$textarea.height(minheight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// And this line is to catch the browser paste event
|
|
||||||
$textarea.on("input paste", function(e){ setTimeout( update, 250); });
|
|
||||||
|
|
||||||
// Run update once when elastic is initialized
|
|
||||||
update();
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})(jQuery);
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,180 +0,0 @@
|
||||||
/**
|
|
||||||
* jquery.purr.js
|
|
||||||
* Copyright (c) 2008 Net Perspective (net-perspective.com)
|
|
||||||
* Licensed under the MIT License (http://www.opensource.org/licenses/mit-license.php)
|
|
||||||
*
|
|
||||||
* @author R.A. Ray
|
|
||||||
* @projectDescription jQuery plugin for dynamically displaying unobtrusive messages in the browser. Mimics the behavior of the MacOS program "Growl."
|
|
||||||
* @version 0.1.0
|
|
||||||
*
|
|
||||||
* @requires jquery.js (tested with 1.2.6)
|
|
||||||
*
|
|
||||||
* @param fadeInSpeed int - Duration of fade in animation in miliseconds
|
|
||||||
* default: 500
|
|
||||||
* @param fadeOutSpeed int - Duration of fade out animationin miliseconds
|
|
||||||
default: 500
|
|
||||||
* @param removeTimer int - Timeout, in miliseconds, before notice is removed once it is the top non-sticky notice in the list
|
|
||||||
default: 4000
|
|
||||||
* @param isSticky bool - Whether the notice should fade out on its own or wait to be manually closed
|
|
||||||
default: false
|
|
||||||
* @param usingTransparentPNG bool - Whether or not the notice is using transparent .png images in its styling
|
|
||||||
default: false
|
|
||||||
*/
|
|
||||||
|
|
||||||
( function( $ ) {
|
|
||||||
|
|
||||||
$.purr = function ( notice, options )
|
|
||||||
{
|
|
||||||
// Convert notice to a jQuery object
|
|
||||||
notice = $( notice );
|
|
||||||
|
|
||||||
// Add a class to denote the notice as not sticky
|
|
||||||
if ( !options.isSticky )
|
|
||||||
{
|
|
||||||
notice.addClass( 'not-sticky' );
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get the container element from the page
|
|
||||||
var cont = document.getElementById( 'purr-container' );
|
|
||||||
|
|
||||||
// If the container doesn't yet exist, we need to create it
|
|
||||||
if ( !cont )
|
|
||||||
{
|
|
||||||
cont = '<div id="purr-container"></div>';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert cont to a jQuery object
|
|
||||||
cont = $( cont );
|
|
||||||
|
|
||||||
// Add the container to the page
|
|
||||||
$( 'body' ).append( cont );
|
|
||||||
|
|
||||||
notify();
|
|
||||||
|
|
||||||
function notify ()
|
|
||||||
{
|
|
||||||
// Set up the close button
|
|
||||||
var close = document.createElement( 'a' );
|
|
||||||
$( close ).attr(
|
|
||||||
{
|
|
||||||
className: 'close',
|
|
||||||
href: '#close',
|
|
||||||
innerHTML: 'Close'
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.appendTo( notice )
|
|
||||||
.click( function ()
|
|
||||||
{
|
|
||||||
removeNotice();
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add the notice to the page and keep it hidden initially
|
|
||||||
notice.appendTo( cont )
|
|
||||||
.hide();
|
|
||||||
|
|
||||||
if ( jQuery.browser.msie && options.usingTransparentPNG )
|
|
||||||
{
|
|
||||||
// IE7 and earlier can't handle the combination of opacity and transparent pngs, so if we're using transparent pngs in our
|
|
||||||
// notice style, we'll just skip the fading in.
|
|
||||||
notice.show();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//Fade in the notice we just added
|
|
||||||
notice.fadeIn( options.fadeInSpeed );
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up the removal interval for the added notice if that notice is not a sticky
|
|
||||||
if ( !options.isSticky )
|
|
||||||
{
|
|
||||||
var topSpotInt = setInterval( function ()
|
|
||||||
{
|
|
||||||
// Check to see if our notice is the first non-sticky notice in the list
|
|
||||||
if ( notice.prevAll( '.not-sticky' ).length == 0 )
|
|
||||||
{
|
|
||||||
// Stop checking once the condition is met
|
|
||||||
clearInterval( topSpotInt );
|
|
||||||
|
|
||||||
// Call the close action after the timeout set in options
|
|
||||||
setTimeout( function ()
|
|
||||||
{
|
|
||||||
removeNotice();
|
|
||||||
}, options.removeTimer
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, 200 );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeNotice ()
|
|
||||||
{
|
|
||||||
// IE7 and earlier can't handle the combination of opacity and transparent pngs, so if we're using transparent pngs in our
|
|
||||||
// notice style, we'll just skip the fading out.
|
|
||||||
if ( jQuery.browser.msie && options.usingTransparentPNG )
|
|
||||||
{
|
|
||||||
notice.css( { opacity: 0 } )
|
|
||||||
.animate(
|
|
||||||
{
|
|
||||||
height: '0px'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
duration: options.fadeOutSpeed,
|
|
||||||
complete: function ()
|
|
||||||
{
|
|
||||||
notice.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Fade the object out before reducing its height to produce the sliding effect
|
|
||||||
notice.animate(
|
|
||||||
{
|
|
||||||
opacity: '0'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
duration: options.fadeOutSpeed,
|
|
||||||
complete: function ()
|
|
||||||
{
|
|
||||||
notice.animate(
|
|
||||||
{
|
|
||||||
height: '0px'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
duration: options.fadeOutSpeed,
|
|
||||||
complete: function ()
|
|
||||||
{
|
|
||||||
notice.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
$.fn.purr = function ( options )
|
|
||||||
{
|
|
||||||
options = options || {};
|
|
||||||
options.fadeInSpeed = options.fadeInSpeed || 500;
|
|
||||||
options.fadeOutSpeed = options.fadeOutSpeed || 500;
|
|
||||||
options.removeTimer = options.removeTimer || 4000;
|
|
||||||
options.isSticky = options.isSticky || false;
|
|
||||||
options.usingTransparentPNG = options.usingTransparentPNG || false;
|
|
||||||
|
|
||||||
this.each( function()
|
|
||||||
{
|
|
||||||
new $.purr( this, options );
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
})( jQuery );
|
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,23 +0,0 @@
|
||||||
function SocketIoConnection(config) {
|
|
||||||
this.connection = io.connect(config.url, config.socketio);
|
|
||||||
}
|
|
||||||
|
|
||||||
SocketIoConnection.prototype.on = function (ev, fn) {
|
|
||||||
this.connection.on(ev, fn);
|
|
||||||
};
|
|
||||||
|
|
||||||
SocketIoConnection.prototype.emit = function () {
|
|
||||||
this.connection.emit.apply(this.connection, arguments);
|
|
||||||
};
|
|
||||||
|
|
||||||
SocketIoConnection.prototype.removeAllListeners = function () {
|
|
||||||
this.connection.removeAllListeners();
|
|
||||||
};
|
|
||||||
|
|
||||||
SocketIoConnection.prototype.getSessionid = function () {
|
|
||||||
return this.connection.socket.sessionid;
|
|
||||||
};
|
|
||||||
|
|
||||||
SocketIoConnection.prototype.disconnect = function () {
|
|
||||||
return this.connection.disconnect();
|
|
||||||
};
|
|
|
@ -1,41 +0,0 @@
|
||||||
var USERVOICE;
|
|
||||||
if(USERVOICE == undefined) {
|
|
||||||
USERVOICE = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
USERVOICE.load = function (name, id, email, sso_token) {
|
|
||||||
// Include the UserVoice JavaScript SDK (only needed once on a page)
|
|
||||||
UserVoice=window.UserVoice||[];(function(){var uv=document.createElement('script');uv.type='text/javascript';uv.async=true;uv.src='//widget.uservoice.com/wybK0nSMNuhlWkIKzTyWg.js';var s=document.getElementsByTagName('script')[0];s.parentNode.insertBefore(uv,s)})();
|
|
||||||
|
|
||||||
//
|
|
||||||
// UserVoice Javascript SDK developer documentation:
|
|
||||||
// https://www.uservoice.com/o/javascript-sdk
|
|
||||||
//
|
|
||||||
|
|
||||||
// Set colors
|
|
||||||
UserVoice.push(['set', {
|
|
||||||
accent_color: '#448dd6',
|
|
||||||
trigger_color: 'white',
|
|
||||||
trigger_background_color: 'rgba(46, 49, 51, 0.6)'
|
|
||||||
}]);
|
|
||||||
|
|
||||||
// Identify the user and pass traits
|
|
||||||
// To enable, replace sample data with actual user traits and uncomment the line
|
|
||||||
if (name) {
|
|
||||||
UserVoice.push(['setSSO', sso_token]);
|
|
||||||
UserVoice.push(['identify', {
|
|
||||||
'email': email, // User’s email address
|
|
||||||
'name': name, // User’s real name
|
|
||||||
'id': id, // Optional: Unique id of the user
|
|
||||||
}]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add default trigger to the bottom-left corner of the window:
|
|
||||||
UserVoice.push(['addTrigger', { mode: 'contact', trigger_position: 'bottom-left' }]);
|
|
||||||
|
|
||||||
// Or, use your own custom trigger:
|
|
||||||
//UserVoice.push(['addTrigger', '#barometer_tab', { mode: 'contact' }]);
|
|
||||||
|
|
||||||
// Autoprompt for Satisfaction and SmartVote (only displayed under certain conditions)
|
|
||||||
UserVoice.push(['autoprompt', {}]);
|
|
||||||
};
|
|
|
@ -15,6 +15,7 @@ Metamaps.Erb['icons/wildcard.png'] = '<%= asset_path('icons/wildcard.png') %>'
|
||||||
Metamaps.Erb['topic_description_signifier.png'] = '<%= asset_path('topic_description_signifier.png') %>'
|
Metamaps.Erb['topic_description_signifier.png'] = '<%= asset_path('topic_description_signifier.png') %>'
|
||||||
Metamaps.Erb['topic_link_signifier.png'] = '<%= asset_path('topic_link_signifier.png') %>'
|
Metamaps.Erb['topic_link_signifier.png'] = '<%= asset_path('topic_link_signifier.png') %>'
|
||||||
Metamaps.Erb['synapse16.png'] = '<%= asset_path('synapse16.png') %>'
|
Metamaps.Erb['synapse16.png'] = '<%= asset_path('synapse16.png') %>'
|
||||||
|
Metamaps.Erb['import-example.png'] = '<%= asset_path('import-example.png') %>'
|
||||||
Metamaps.Erb['sounds/MM_sounds.mp3'] = '<%= asset_path 'sounds/MM_sounds.mp3' %>'
|
Metamaps.Erb['sounds/MM_sounds.mp3'] = '<%= asset_path 'sounds/MM_sounds.mp3' %>'
|
||||||
Metamaps.Erb['sounds/MM_sounds.ogg'] = '<%= asset_path 'sounds/MM_sounds.ogg' %>'
|
Metamaps.Erb['sounds/MM_sounds.ogg'] = '<%= asset_path 'sounds/MM_sounds.ogg' %>'
|
||||||
Metamaps.Metacodes = <%= Metacode.all.to_json.gsub(%r[(icon.*?)(\"},)], '\1?purple=stupid\2').html_safe %>
|
Metamaps.Metacodes = <%= Metacode.all.to_json.gsub(%r[(icon.*?)(\"},)], '\1?purple=stupid\2').html_safe %>
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
// TODO document this user agent function
|
|
||||||
var labelType, useGradients, nativeTextSupport, animate
|
|
||||||
;(function () {
|
|
||||||
var ua = navigator.userAgent,
|
|
||||||
iStuff = ua.match(/iPhone/i) || ua.match(/iPad/i),
|
|
||||||
typeOfCanvas = typeof HTMLCanvasElement,
|
|
||||||
nativeCanvasSupport = (typeOfCanvas == 'object' || typeOfCanvas == 'function'),
|
|
||||||
textSupport = nativeCanvasSupport && (typeof document.createElement('canvas').getContext('2d').fillText == 'function')
|
|
||||||
// I'm setting this based on the fact that ExCanvas provides text support for IE
|
|
||||||
// and that as of today iPhone/iPad current text support is lame
|
|
||||||
labelType = (!nativeCanvasSupport || (textSupport && !iStuff)) ? 'Native' : 'HTML'
|
|
||||||
nativeTextSupport = labelType == 'Native'
|
|
||||||
useGradients = nativeCanvasSupport
|
|
||||||
animate = !(iStuff || !nativeCanvasSupport)
|
|
||||||
})()
|
|
|
@ -1526,9 +1526,8 @@ h3.filterBox {
|
||||||
background-image: url(<%= asset_data_uri('permissions32_sprite.png') %>);
|
background-image: url(<%= asset_data_uri('permissions32_sprite.png') %>);
|
||||||
}
|
}
|
||||||
/* map info box */
|
/* map info box */
|
||||||
/* map info box */
|
|
||||||
|
|
||||||
.wrapper div.mapInfoBox {
|
.wrapper .mapInfoBox {
|
||||||
display: none;
|
display: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 40px;
|
bottom: 40px;
|
||||||
|
@ -1536,12 +1535,40 @@ h3.filterBox {
|
||||||
background-color: #424242;
|
background-color: #424242;
|
||||||
color: #F5F5F5;
|
color: #F5F5F5;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
|
box-shadow: 0 3px 3px rgba(0,0,0,0.23), 0px 3px 3px rgba(0,0,0,0.16);
|
||||||
|
text-align: center;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
.import-dialog{
|
||||||
|
button {
|
||||||
|
margin: 1em 0.5em;
|
||||||
|
}
|
||||||
|
.import-blue-button {
|
||||||
|
display: inline-block;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0.75em;
|
||||||
|
padding: 0.75em;
|
||||||
|
height: 3em;
|
||||||
|
background-color: #AAB0FB;
|
||||||
|
border-radius: 0.3em;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.fileupload {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0.75em;
|
||||||
|
padding: 0.75em;
|
||||||
|
height: 3em;
|
||||||
|
border: 3px dashed #AAB0FB;
|
||||||
|
width: 75%;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.wrapper .mapInfoBox {
|
||||||
width: 360px;
|
width: 360px;
|
||||||
min-height: 300px;
|
min-height: 300px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
font-style: normal;
|
|
||||||
text-align: center;
|
|
||||||
box-shadow: 0 3px 3px rgba(0,0,0,0.23), 0px 3px 3px rgba(0,0,0,0.16);
|
|
||||||
}
|
}
|
||||||
.requestTitle {
|
.requestTitle {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -2028,17 +2055,17 @@ and it won't be important on password protected instances */
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: fixed;
|
position: absolute;
|
||||||
z-index: 1000000;
|
z-index: 1000000;
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
#lightbox_main {
|
#lightbox_main {
|
||||||
width: 800px;
|
width: 800px;
|
||||||
height: auto;
|
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
position: relative;
|
position: relative;
|
||||||
top: 50%;
|
top: 5vh;
|
||||||
|
height: 90vh;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
color: black;
|
color: black;
|
||||||
}
|
}
|
||||||
|
@ -2077,8 +2104,11 @@ and it won't be important on password protected instances */
|
||||||
background-position: center center;
|
background-position: center center;
|
||||||
}
|
}
|
||||||
#lightbox_content {
|
#lightbox_content {
|
||||||
width: 552px;
|
width: 800px;
|
||||||
height: 434px;
|
height: 500px;
|
||||||
|
max-height: 90vh;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow-y: auto;
|
||||||
background-color: #e0e0e0;
|
background-color: #e0e0e0;
|
||||||
padding: 64px 124px 64px 124px;
|
padding: 64px 124px 64px 124px;
|
||||||
box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23), 10px 10px 10px rgba(0, 0, 0, 0.19);
|
box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23), 10px 10px 10px rgba(0, 0, 0, 0.19);
|
|
@ -17,7 +17,6 @@
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#center-container {
|
#center-container {
|
||||||
position:relative;
|
position:relative;
|
||||||
height:100%;
|
height:100%;
|
||||||
|
@ -143,6 +142,16 @@
|
||||||
margin-top:5px;
|
margin-top:5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.CardOnGraph .desc ol,
|
||||||
|
.CardOnGraph .desc ul {
|
||||||
|
margin-left: 1em;
|
||||||
|
|
||||||
|
}
|
||||||
|
.CardOnGraph .desc a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
.CardOnGraph .best_in_place_desc {
|
.CardOnGraph .best_in_place_desc {
|
||||||
display:block;
|
display:block;
|
||||||
margin-top:2px;
|
margin-top:2px;
|
||||||
|
@ -582,10 +591,10 @@ background-color: #E0E0E0;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.CardOnGraph .hoverForTip:hover .tip, .mapCard .hoverForTip:hover .tip, #mapContribs:hover .tip {
|
.CardOnGraph .hoverForTip:hover .tip, #mapContribs:hover .tip {
|
||||||
display:block;
|
display:block;
|
||||||
}
|
}
|
||||||
.CardOnGraph .tip, .mapCard .tip {
|
.CardOnGraph .tip {
|
||||||
display:none;
|
display:none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background: black;
|
background: black;
|
||||||
|
@ -942,154 +951,7 @@ font-family: 'din-regular', helvetica, sans-serif;
|
||||||
background-position: 0 -24px;
|
background-position: 0 -24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Map Cards */
|
|
||||||
|
|
||||||
.map {
|
|
||||||
display:inline-block;
|
|
||||||
width:220px;
|
|
||||||
height:340px;
|
|
||||||
font-size: 12px;
|
|
||||||
text-align: left;
|
|
||||||
overflow: visible;
|
|
||||||
background: #e8e8e8;
|
|
||||||
border-radius:2px;
|
|
||||||
margin:16px 16px 16px 19px;
|
|
||||||
box-shadow: 0px 3px 3px rgba(0,0,0,0.23), 0 3px 3px rgba(0,0,0,0.16);
|
|
||||||
}
|
|
||||||
.map:hover {
|
|
||||||
background: #dcdcdc;
|
|
||||||
}
|
|
||||||
.map.newMap {
|
|
||||||
float: left;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.map.newMap a {
|
|
||||||
height: 340px;
|
|
||||||
display: block;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.newMap .newMapImage {
|
|
||||||
display: block;
|
|
||||||
width: 72px;
|
|
||||||
height: 72px;
|
|
||||||
background-image: url("<%= asset_data_uri('newmap_sprite.png') %>");
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: 0 0;
|
|
||||||
position: absolute;
|
|
||||||
left: 50%;
|
|
||||||
margin-left: -36px;
|
|
||||||
top: 50%;
|
|
||||||
margin-top: -36px;
|
|
||||||
}
|
|
||||||
.map:hover .newMapImage {
|
|
||||||
background-position: 0 -72px;
|
|
||||||
}
|
|
||||||
.newMap span {
|
|
||||||
font-family: 'din-regular', sans-serif;
|
|
||||||
font-size: 18px;
|
|
||||||
line-height: 22px;
|
|
||||||
text-align: center;
|
|
||||||
display: block;
|
|
||||||
padding-top: 220px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mapCard {
|
|
||||||
display: -webkit-box; /* OLD - iOS 6-, Safari 3.1-6 */
|
|
||||||
display: -moz-box; /* OLD - Firefox 19- (buggy but mostly works) */
|
|
||||||
display: -ms-flexbox; /* TWEENER - IE 10 */
|
|
||||||
display: -webkit-flex; /* NEW - Chrome */
|
|
||||||
display: flex; /* NEW, Spec - Opera 12.1, Firefox 20+ */
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
-moz-box-orient: vertical;
|
|
||||||
-webkit-box-direction: normal;
|
|
||||||
-moz-box-direction: normal;
|
|
||||||
-ms-flex-direction: column;
|
|
||||||
-webkit-flex-direction: column;
|
|
||||||
flex-direction: column;
|
|
||||||
position:relative;
|
|
||||||
width:100%;
|
|
||||||
height:308px;
|
|
||||||
padding: 16px 0;
|
|
||||||
color: #424242;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mapCard .title {
|
|
||||||
word-wrap: break-word;
|
|
||||||
font-size:18px;
|
|
||||||
line-height:22px;
|
|
||||||
height: 44px;
|
|
||||||
display:block;
|
|
||||||
padding: 0 16px;
|
|
||||||
text-align: center;
|
|
||||||
-webkit-box-flex: none; /* OLD - iOS 6-, Safari 3.1-6 */
|
|
||||||
-moz-box-flex: none; /* OLD - Firefox 19- */
|
|
||||||
-webkit-flex: none; /* Chrome */
|
|
||||||
-ms-flex: none; /* IE 10 */
|
|
||||||
flex: none; /* NEW, Spec - Opera 12.1, Firefox 20+ */
|
|
||||||
font-family: 'din-regular', sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mapCard .mapScreenshot {
|
|
||||||
width: 188px;
|
|
||||||
height: 126px;
|
|
||||||
padding: 8px 16px;
|
|
||||||
}
|
|
||||||
.mapCard .mapScreenshot img {
|
|
||||||
width: 188px;
|
|
||||||
height: 126px;
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mapCard .scroll {
|
|
||||||
display:block;
|
|
||||||
-webkit-box-flex: 1; /* OLD - iOS 6-, Safari 3.1-6 */
|
|
||||||
-moz-box-flex: 1; /* OLD - Firefox 19- */
|
|
||||||
-webkit-flex: 1; /* Chrome */
|
|
||||||
-ms-flex: 1; /* IE 10 */
|
|
||||||
flex: 1; /* NEW, Spec - Opera 12.1, Firefox 20+ */
|
|
||||||
padding:0 16px 8px;
|
|
||||||
font-family: helvetica, sans-serif;
|
|
||||||
font-style: italic;
|
|
||||||
font-size: 12px;
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
.mCS_no_scrollbar {
|
|
||||||
padding-right: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mapCard .mapMetadata {
|
|
||||||
font-family: 'din-regular', sans-serif;
|
|
||||||
font-size: 12px;
|
|
||||||
position:relative;
|
|
||||||
border-top: 1px solid #BDBDBD;
|
|
||||||
-webkit-box-flex: none; /* OLD - iOS 6-, Safari 3.1-6 */
|
|
||||||
-moz-box-flex: none; /* OLD - Firefox 19- */
|
|
||||||
-webkit-flex: none; /* Chrome */
|
|
||||||
-ms-flex: none; /* IE 10 */
|
|
||||||
flex: none; /* NEW, Spec - Opera 12.1, Firefox 20+ */
|
|
||||||
}
|
|
||||||
|
|
||||||
.mapCard .metadataSection {
|
|
||||||
padding: 8px 16px 0 16px;
|
|
||||||
width: 78px;
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mapPermission {
|
|
||||||
font-family: 'din-medium', sans-serif;
|
|
||||||
}
|
|
||||||
.cCountColor {
|
|
||||||
font-family: 'din-medium', sans-serif;
|
|
||||||
color: #DB5D5D;
|
|
||||||
}
|
|
||||||
.tCountColor {
|
|
||||||
font-family: 'din-medium', sans-serif;
|
|
||||||
color: #4FC059;
|
|
||||||
}
|
|
||||||
.sCountColor {
|
|
||||||
font-family: 'din-medium', sans-serif;
|
|
||||||
color: #DAB539;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* mapper card */
|
/* mapper card */
|
||||||
|
|
|
@ -188,7 +188,7 @@
|
||||||
.upperRightIcon {
|
.upperRightIcon {
|
||||||
width: 32px;
|
width: 32px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
background-image: url(<%= asset_data_uri('topright_sprite.png') %>);
|
background-image: url(<%= asset_path('topright_sprite.png') %>);
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
@ -325,7 +325,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.fullWidthWrapper.withPartners {
|
.fullWidthWrapper.withPartners {
|
||||||
background: url(<%= asset_data_uri('homepage_bg_fade.png') %>) no-repeat center -300px;
|
background: url(<%= asset_path('homepage_bg_fade.png') %>) no-repeat center -300px;
|
||||||
}
|
}
|
||||||
.homeWrapper.homePartners {
|
.homeWrapper.homePartners {
|
||||||
padding: 64px 0 280px;
|
padding: 64px 0 280px;
|
||||||
|
@ -364,7 +364,7 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.openCheatsheet {
|
.openCheatsheet {
|
||||||
background-image: url(<%= asset_data_uri('help_sprite.png') %>);
|
background-image: url(<%= asset_path('help_sprite.png') %>);
|
||||||
background-repeat:no-repeat;
|
background-repeat:no-repeat;
|
||||||
}
|
}
|
||||||
.openCheatsheet:hover {
|
.openCheatsheet:hover {
|
||||||
|
@ -373,7 +373,7 @@
|
||||||
.mapInfoIcon {
|
.mapInfoIcon {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: 56px; /* puts it just offscreen */
|
top: 56px; /* puts it just offscreen */
|
||||||
background-image: url(<%= asset_data_uri('mapinfo_sprite.png') %>);
|
background-image: url(<%= asset_path('mapinfo_sprite.png') %>);
|
||||||
background-repeat:no-repeat;
|
background-repeat:no-repeat;
|
||||||
}
|
}
|
||||||
.mapInfoIcon:hover {
|
.mapInfoIcon:hover {
|
||||||
|
@ -382,8 +382,14 @@
|
||||||
.mapPage .mapInfoIcon {
|
.mapPage .mapInfoIcon {
|
||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
|
.importDialog {
|
||||||
|
background-image: url(<%= asset_path('import.png') %>);
|
||||||
|
background-position: 0 0;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
width: 32px;
|
||||||
|
}
|
||||||
.starMap {
|
.starMap {
|
||||||
background-image: url(<%= asset_data_uri('starmap_sprite.png') %>);
|
background-image: url(<%= asset_path('starmap_sprite.png') %>);
|
||||||
background-position: 0 0;
|
background-position: 0 0;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
width: 32px;
|
width: 32px;
|
||||||
|
@ -437,7 +443,7 @@
|
||||||
.takeScreenshot {
|
.takeScreenshot {
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
background-image: url(<%= asset_data_uri 'screenshot_sprite.png' %>);
|
background-image: url(<%= asset_path 'screenshot_sprite.png' %>);
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
.takeScreenshot:hover {
|
.takeScreenshot:hover {
|
||||||
|
@ -450,7 +456,7 @@
|
||||||
.zoomExtents {
|
.zoomExtents {
|
||||||
margin-bottom:5px;
|
margin-bottom:5px;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
background-image: url(<%= asset_data_uri('extents_sprite.png') %>);
|
background-image: url(<%= asset_path('extents_sprite.png') %>);
|
||||||
}
|
}
|
||||||
|
|
||||||
.zoomExtents:hover {
|
.zoomExtents:hover {
|
||||||
|
@ -458,7 +464,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.zoomExtents:hover .tooltips, .zoomIn:hover .tooltips, .zoomOut:hover .tooltips, .takeScreenshot:hover .tooltips, .sidebarFilterIcon:hover .tooltipsUnder, .sidebarForkIcon:hover .tooltipsUnder, .addMap:hover .tooltipsUnder, .authenticated .sidebarAccountIcon:hover .tooltipsUnder,
|
.zoomExtents:hover .tooltips, .zoomIn:hover .tooltips, .zoomOut:hover .tooltips, .takeScreenshot:hover .tooltips, .sidebarFilterIcon:hover .tooltipsUnder, .sidebarForkIcon:hover .tooltipsUnder, .addMap:hover .tooltipsUnder, .authenticated .sidebarAccountIcon:hover .tooltipsUnder,
|
||||||
.mapInfoIcon:hover .tooltipsAbove, .openCheatsheet:hover .tooltipsAbove, .chat-button:hover .tooltips, .starMap:hover .tooltipsAbove, .openMetacodeSwitcher:hover .tooltipsAbove, .pinCarousel:not(.isPinned):hover .tooltipsAbove.helpPin, .pinCarousel.isPinned:hover .tooltipsAbove.helpUnpin {
|
.mapInfoIcon:hover .tooltipsAbove, .openCheatsheet:hover .tooltipsAbove, .chat-button:hover .tooltips, importDialog:hover .tooltipsAbove, .starMap:hover .tooltipsAbove, .openMetacodeSwitcher:hover .tooltipsAbove, .pinCarousel:not(.isPinned):hover .tooltipsAbove.helpPin, .pinCarousel.isPinned:hover .tooltipsAbove.helpUnpin {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -623,7 +629,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.zoomIn {
|
.zoomIn {
|
||||||
background-image: url(<%= asset_data_uri('zoom_sprite.png') %>);
|
background-image: url(<%= asset_path('zoom_sprite.png') %>);
|
||||||
background-position: 0 /…0;
|
background-position: 0 /…0;
|
||||||
border-top-left-radius: 2px;
|
border-top-left-radius: 2px;
|
||||||
border-top-right-radius: 2px;
|
border-top-right-radius: 2px;
|
||||||
|
@ -632,7 +638,7 @@
|
||||||
background-position: -32px 0;
|
background-position: -32px 0;
|
||||||
}
|
}
|
||||||
.zoomOut {
|
.zoomOut {
|
||||||
background-image: url(<%= asset_data_uri('zoom_sprite.png') %>);
|
background-image: url(<%= asset_path('zoom_sprite.png') %>);
|
||||||
background-position:0 -32px;
|
background-position:0 -32px;
|
||||||
border-bottom-left-radius: 2px;
|
border-bottom-left-radius: 2px;
|
||||||
border-bottom-right-radius: 2px;
|
border-bottom-right-radius: 2px;
|
||||||
|
@ -650,15 +656,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
#exploreMaps {
|
#exploreMaps {
|
||||||
padding: 0 5%;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 90%;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#exploreMaps > div {
|
#exploreMaps > div {
|
||||||
margin-top: 110px;
|
margin: 110px auto 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.loadMore {
|
.button.loadMore {
|
||||||
|
@ -740,23 +745,23 @@
|
||||||
left:5px;
|
left:5px;
|
||||||
}
|
}
|
||||||
.exploreMapsCenter .myMaps .exploreMapsIcon {
|
.exploreMapsCenter .myMaps .exploreMapsIcon {
|
||||||
background-image: url(<%= asset_data_uri 'exploremaps_sprite.png' %>);
|
background-image: url(<%= asset_path 'exploremaps_sprite.png' %>);
|
||||||
background-position: -32px 0;
|
background-position: -32px 0;
|
||||||
}
|
}
|
||||||
.exploreMapsCenter .sharedMaps .exploreMapsIcon {
|
.exploreMapsCenter .sharedMaps .exploreMapsIcon {
|
||||||
background-image: url(<%= asset_data_uri 'exploremaps_sprite.png' %>);
|
background-image: url(<%= asset_path 'exploremaps_sprite.png' %>);
|
||||||
background-position: -128px 0;
|
background-position: -128px 0;
|
||||||
}
|
}
|
||||||
.exploreMapsCenter .activeMaps .exploreMapsIcon {
|
.exploreMapsCenter .activeMaps .exploreMapsIcon {
|
||||||
background-image: url(<%= asset_data_uri 'exploremaps_sprite.png' %>);
|
background-image: url(<%= asset_path 'exploremaps_sprite.png' %>);
|
||||||
background-position: 0 0;
|
background-position: 0 0;
|
||||||
}
|
}
|
||||||
.exploreMapsCenter .featuredMaps .exploreMapsIcon {
|
.exploreMapsCenter .featuredMaps .exploreMapsIcon {
|
||||||
background-image: url(<%= asset_data_uri 'exploremaps_sprite.png' %>);
|
background-image: url(<%= asset_path 'exploremaps_sprite.png' %>);
|
||||||
background-position: -96px 0;
|
background-position: -96px 0;
|
||||||
}
|
}
|
||||||
.exploreMapsCenter .starredMaps .exploreMapsIcon {
|
.exploreMapsCenter .starredMaps .exploreMapsIcon {
|
||||||
background-image: url(<%= asset_data_uri 'exploremaps_sprite.png' %>);
|
background-image: url(<%= asset_path 'exploremaps_sprite.png' %>);
|
||||||
background-position: -96px 0;
|
background-position: -96px 0;
|
||||||
}
|
}
|
||||||
.myMaps:hover .exploreMapsIcon, .myMaps.active .exploreMapsIcon {
|
.myMaps:hover .exploreMapsIcon, .myMaps.active .exploreMapsIcon {
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
/* =USERVOICE ICON DEFINE
|
.unauthenticated .feedback-icon {
|
||||||
--------------------------------------------------------*/
|
|
||||||
|
|
||||||
.unauthenticated .uv-icon {
|
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.uv-icon.uv-bottom-left {
|
.feedback-icon {
|
||||||
|
position: fixed;
|
||||||
background-image: url(<%= asset_data_uri 'feedback_sprite.png' %>);
|
background-image: url(<%= asset_data_uri 'feedback_sprite.png' %>);
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
color:#FFFFFF;
|
color:#FFFFFF;
|
||||||
|
@ -20,6 +18,8 @@ div.uv-icon.uv-bottom-left {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.uv-icon.uv-bottom-left:hover {
|
.feedback-icon:hover {
|
||||||
background-position: 0 -110px;
|
background-position: 0 -110px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
259
app/assets/stylesheets/mapcard.scss.erb
Normal file
259
app/assets/stylesheets/mapcard.scss.erb
Normal file
|
@ -0,0 +1,259 @@
|
||||||
|
/* Map Cards */
|
||||||
|
|
||||||
|
.map {
|
||||||
|
display:inline-block;
|
||||||
|
width:220px;
|
||||||
|
height:340px;
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: left;
|
||||||
|
overflow: visible;
|
||||||
|
background: #e8e8e8;
|
||||||
|
border-radius:2px;
|
||||||
|
margin:16px;
|
||||||
|
box-shadow: 0px 3px 3px rgba(0,0,0,0.23), 0 3px 3px rgba(0,0,0,0.16);
|
||||||
|
|
||||||
|
&.newMap {
|
||||||
|
float: left;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #dcdcdc;
|
||||||
|
|
||||||
|
.newMapImage {
|
||||||
|
background-position: 0 -72px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
height: 340px;
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.newMapImage {
|
||||||
|
display: block;
|
||||||
|
width: 72px;
|
||||||
|
height: 72px;
|
||||||
|
background-image: url("<%= asset_data_uri('newmap_sprite.png') %>");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: 0 0;
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -36px;
|
||||||
|
top: 50%;
|
||||||
|
margin-top: -36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-family: 'din-regular', sans-serif;
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 22px;
|
||||||
|
text-align: center;
|
||||||
|
display: block;
|
||||||
|
padding-top: 220px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mapCard {
|
||||||
|
position:relative;
|
||||||
|
width:100%;
|
||||||
|
height:308px;
|
||||||
|
padding: 0 0 16px 0;
|
||||||
|
color: #424242;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.dropdownMenu .menuToggle .circle {
|
||||||
|
background-color: #FFF;
|
||||||
|
}
|
||||||
|
.dropdownMenu .menuToggle:hover .circle {
|
||||||
|
background-color: #DDD;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mainContent {
|
||||||
|
filter: blur(2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mapMetadata {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mapHasMapper, .mapHasConversation {
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
left: 8px;
|
||||||
|
min-width: 32px;
|
||||||
|
min-height: 32px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #FFF;
|
||||||
|
border-radius: 2px;
|
||||||
|
|
||||||
|
.mapperList {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mapperList {
|
||||||
|
display: none;
|
||||||
|
padding: 8px;
|
||||||
|
list-style-type: none;
|
||||||
|
|
||||||
|
li {
|
||||||
|
&.live {
|
||||||
|
height: 32px;
|
||||||
|
padding-left: 32px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 12px;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
padding-left: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.mapHasMapper {
|
||||||
|
background: url('<%= asset_path('junto.png') %>') no-repeat 4px 0;
|
||||||
|
}
|
||||||
|
.mapHasConversation {
|
||||||
|
background: url('<%= asset_path('junto.gif') %>') no-repeat 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdownMenu {
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
right: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.menuToggle {
|
||||||
|
width: 30px;
|
||||||
|
height: 10px;
|
||||||
|
|
||||||
|
.circle {
|
||||||
|
display: inline-block;
|
||||||
|
background-color: #454545;
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
margin: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover .circle {
|
||||||
|
background-color: #222;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuItems {
|
||||||
|
position: absolute;
|
||||||
|
top: 18px;
|
||||||
|
right: 0px;
|
||||||
|
background: #FFF;
|
||||||
|
border-radius: 2px;
|
||||||
|
list-style-type: none;
|
||||||
|
color: #454545;
|
||||||
|
|
||||||
|
li {
|
||||||
|
white-space: nowrap;
|
||||||
|
padding: 6px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #DDD;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mapScreenshot {
|
||||||
|
width: 100%;
|
||||||
|
height: 220px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mapScreenshot img {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
word-wrap: break-word;
|
||||||
|
font-size:18px;
|
||||||
|
line-height:22px;
|
||||||
|
height: 71px;
|
||||||
|
display:table;
|
||||||
|
padding: 0 16px;
|
||||||
|
font-family: 'din-regular', sans-serif;
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
.innerTitle {
|
||||||
|
display: table-cell;
|
||||||
|
vertical-align: middle;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.creatorAndPerm {
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.creatorImage {
|
||||||
|
display: inline-block;
|
||||||
|
border-radius: 16px;
|
||||||
|
vertical-align: middle;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.creatorName {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cardViewOnly {
|
||||||
|
float: right;
|
||||||
|
line-height: 32px;
|
||||||
|
padding-right: 10px;
|
||||||
|
color: #454545;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll {
|
||||||
|
display:block;
|
||||||
|
font-family: helvetica, sans-serif;
|
||||||
|
font-size: 12px;
|
||||||
|
word-wrap: break-word;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.mapMetadata {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
padding: 40px 20px 0;
|
||||||
|
height: 300px;
|
||||||
|
font-family: 'din-regular', sans-serif;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #FFF;
|
||||||
|
background: -moz-linear-gradient(top, rgba(0,0,0,0.65) 0%, rgba(0,0,0,0.43) 81%, rgba(0,0,0,0) 100%);
|
||||||
|
background: -webkit-linear-gradient(top, rgba(0,0,0,0.65) 0%,rgba(0,0,0,0.43) 81%,rgba(0,0,0,0) 100%);
|
||||||
|
background: linear-gradient(to bottom, rgba(0,0,0,0.65) 0%,rgba(0,0,0,0.43) 81%,rgba(0,0,0,0) 100%);
|
||||||
|
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#a6000000', endColorstr='#00000000',GradientType=0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
.metadataSection {
|
||||||
|
padding: 16px 0;
|
||||||
|
width: 90px;
|
||||||
|
float: left;
|
||||||
|
font-family: 'din-medium', sans-serif;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -56,7 +56,7 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wrapper div.mapInfoBox {
|
.wrapper .mapInfoBox {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 50px;
|
top: 50px;
|
||||||
right: 0px;
|
right: 0px;
|
||||||
|
|
98
app/assets/stylesheets/request_access.scss.erb
Normal file
98
app/assets/stylesheets/request_access.scss.erb
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
.viewOnly {
|
||||||
|
float: left;
|
||||||
|
margin-left: 16px;
|
||||||
|
display: none;
|
||||||
|
height: 32px;
|
||||||
|
border: 1px solid #BDBDBD;
|
||||||
|
border-radius: 2px;
|
||||||
|
background-color: #424242;
|
||||||
|
color: #FFF;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 32px;
|
||||||
|
|
||||||
|
&.isViewOnly {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eyeball {
|
||||||
|
background: url('<%= asset_path('view-only.png') %>') no-repeat 4px 0;
|
||||||
|
padding-left: 40px;
|
||||||
|
border-right: #747474;
|
||||||
|
padding-right: 10px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.requestNotice {
|
||||||
|
display: none;
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.requestAccess {
|
||||||
|
background-color: #a354cd;
|
||||||
|
&:hover {
|
||||||
|
background-color: #9150bc;
|
||||||
|
}
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.requestPending {
|
||||||
|
background-color: #4fc059;
|
||||||
|
}
|
||||||
|
|
||||||
|
.requestNotAccepted {
|
||||||
|
background-color: #c04f4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.sendRequest .requestAccess {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
&.sentRequest .requestPending {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
&.requestDenied .requestNotAccepted {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.request_access {
|
||||||
|
position: absolute;
|
||||||
|
width: 90%;
|
||||||
|
margin: 0 5%;
|
||||||
|
|
||||||
|
.monkey {
|
||||||
|
width: 250px;
|
||||||
|
height: 250px;
|
||||||
|
border: 6px solid #424242;
|
||||||
|
border-radius: 125px;
|
||||||
|
background: url(https://s3.amazonaws.com/metamaps-assets/site/monkeyselfie.jpg) no-repeat;
|
||||||
|
background-position: 50% 20%;
|
||||||
|
background-size: 100%;
|
||||||
|
margin: 80px auto 20px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.explainer_text {
|
||||||
|
padding: 0 20% 0 20%;
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 30px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.make_request {
|
||||||
|
background-color: #a354cd;
|
||||||
|
display: block;
|
||||||
|
width: 220px;
|
||||||
|
height: 14px;
|
||||||
|
padding: 16px 0;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 2px;
|
||||||
|
font-size: 14px;
|
||||||
|
box-shadow: 0px 1px 1.5px rgba(0,0,0,0.12), 0 1px 1px rgba(0,0,0,0.24);
|
||||||
|
margin: 0 auto 20px auto;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #FFFFFF !important;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
98
app/controllers/access_controller.rb
Normal file
98
app/controllers/access_controller.rb
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
class AccessController < ApplicationController
|
||||||
|
before_action :require_user, only: [:access, :access_request, :approve_access, :approve_access_post,
|
||||||
|
:deny_access, :deny_access_post, :request_access]
|
||||||
|
before_action :set_map, only: [:access, :access_request, :approve_access, :approve_access_post,
|
||||||
|
:deny_access, :deny_access_post, :request_access]
|
||||||
|
after_action :verify_authorized
|
||||||
|
|
||||||
|
|
||||||
|
# GET maps/:id/request_access
|
||||||
|
def request_access
|
||||||
|
@map = nil
|
||||||
|
respond_to do |format|
|
||||||
|
format.html do
|
||||||
|
render 'maps/request_access'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# POST maps/:id/access_request
|
||||||
|
def access_request
|
||||||
|
request = AccessRequest.create(user: current_user, map: @map)
|
||||||
|
# what about push notification to map owner?
|
||||||
|
MapMailer.access_request_email(request, @map).deliver_later
|
||||||
|
|
||||||
|
respond_to do |format|
|
||||||
|
format.json do
|
||||||
|
head :ok
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# POST maps/:id/access
|
||||||
|
def access
|
||||||
|
user_ids = params[:access] || []
|
||||||
|
|
||||||
|
@map.add_new_collaborators(user_ids).each do |user_id|
|
||||||
|
# add_new_collaborators returns array of added users,
|
||||||
|
# who we then send an email to
|
||||||
|
MapMailer.invite_to_edit_email(@map, current_user, User.find(user_id)).deliver_later
|
||||||
|
end
|
||||||
|
@map.remove_old_collaborators(user_ids)
|
||||||
|
|
||||||
|
respond_to do |format|
|
||||||
|
format.json do
|
||||||
|
head :ok
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# GET maps/:id/approve_access/:request_id
|
||||||
|
def approve_access
|
||||||
|
request = AccessRequest.find(params[:request_id])
|
||||||
|
request.approve()
|
||||||
|
respond_to do |format|
|
||||||
|
format.html { redirect_to map_path(@map), notice: 'Request was approved' }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# GET maps/:id/deny_access/:request_id
|
||||||
|
def deny_access
|
||||||
|
request = AccessRequest.find(params[:request_id])
|
||||||
|
request.deny()
|
||||||
|
respond_to do |format|
|
||||||
|
format.html { redirect_to map_path(@map), notice: 'Request was turned down' }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# POST maps/:id/approve_access/:request_id
|
||||||
|
def approve_access_post
|
||||||
|
request = AccessRequest.find(params[:request_id])
|
||||||
|
request.approve()
|
||||||
|
respond_to do |format|
|
||||||
|
format.json do
|
||||||
|
head :ok
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# POST maps/:id/deny_access/:request_id
|
||||||
|
def deny_access_post
|
||||||
|
request = AccessRequest.find(params[:request_id])
|
||||||
|
request.deny()
|
||||||
|
respond_to do |format|
|
||||||
|
format.json do
|
||||||
|
head :ok
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_map
|
||||||
|
@map = Map.find(params[:id])
|
||||||
|
authorize @map
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
|
@ -2,11 +2,11 @@
|
||||||
module Api
|
module Api
|
||||||
module V1
|
module V1
|
||||||
class DeprecatedController < ApplicationController
|
class DeprecatedController < ApplicationController
|
||||||
# rubocop:disable Style/MethodMissing
|
def deprecated
|
||||||
def method_missing
|
render json: {
|
||||||
render json: { error: '/api/v1 is deprecated! Please use /api/v2 instead.' }
|
error: '/api/v1 has been deprecated! Please use /api/v2 instead.'
|
||||||
|
}, status: :gone
|
||||||
end
|
end
|
||||||
# rubocop:enable Style/MethodMissing
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
module Api
|
|
||||||
module V1
|
|
||||||
class MappingsController < DeprecatedController
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,7 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
module Api
|
|
||||||
module V1
|
|
||||||
class MapsController < DeprecatedController
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,7 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
module Api
|
|
||||||
module V1
|
|
||||||
class SynapsesController < DeprecatedController
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,7 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
module Api
|
|
||||||
module V1
|
|
||||||
class TokensController < DeprecatedController
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,7 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
module Api
|
|
||||||
module V1
|
|
||||||
class TopicsController < DeprecatedController
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -29,6 +29,11 @@ module Api
|
||||||
head :no_content
|
head :no_content
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def catch_404
|
||||||
|
skip_authorization
|
||||||
|
render json: { error: '404 Not found' }, status: :not_found
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def accessible_records
|
def accessible_records
|
||||||
|
|
|
@ -18,12 +18,6 @@ module Api
|
||||||
create_action
|
create_action
|
||||||
respond_with_resource
|
respond_with_resource
|
||||||
end
|
end
|
||||||
|
|
||||||
def my_tokens
|
|
||||||
authorize resource_class
|
|
||||||
instantiate_collection
|
|
||||||
respond_with_collection
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -21,23 +21,14 @@ class ApplicationController < ActionController::Base
|
||||||
helper_method :authenticated?
|
helper_method :authenticated?
|
||||||
helper_method :admin?
|
helper_method :admin?
|
||||||
|
|
||||||
def after_sign_in_path_for(resource)
|
|
||||||
sign_in_url = url_for(action: 'new', controller: 'sessions', only_path: false)
|
|
||||||
|
|
||||||
if request.referer == sign_in_url
|
|
||||||
super
|
|
||||||
elsif params[:uv_login] == '1'
|
|
||||||
'http://support.metamaps.cc/login_success?sso=' + current_sso_token
|
|
||||||
else
|
|
||||||
stored_location_for(resource) || request.referer || root_path
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_unauthorized
|
def handle_unauthorized
|
||||||
if authenticated?
|
if authenticated? and params[:controller] == 'maps' and params[:action] == 'show'
|
||||||
head :forbidden # TODO: make this better
|
redirect_to request_access_map_path(params[:id])
|
||||||
|
elsif authenticated?
|
||||||
|
redirect_to root_path, notice: "You don't have permission to see that page."
|
||||||
else
|
else
|
||||||
redirect_to new_user_session_path, notice: 'Try signing in to do that.'
|
store_location_for(resource, request.fullpath)
|
||||||
|
redirect_to sign_in_path, notice: 'Try signing in to do that.'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -55,7 +46,7 @@ class ApplicationController < ActionController::Base
|
||||||
|
|
||||||
def require_user
|
def require_user
|
||||||
return true if authenticated?
|
return true if authenticated?
|
||||||
redirect_to new_user_session_path, notice: 'You must be logged in.'
|
redirect_to sign_in_path, notice: 'You must be logged in.'
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ class ExploreController < ApplicationController
|
||||||
|
|
||||||
# GET /explore/active
|
# GET /explore/active
|
||||||
def active
|
def active
|
||||||
@maps = map_scope(Map.where.not(name: 'Untitled Map'))
|
@maps = map_scope(Map.where.not(name: 'Untitled Map').where.not(permission: 'private'))
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html do
|
format.html do
|
||||||
|
|
|
@ -8,7 +8,7 @@ class MainController < ApplicationController
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html do
|
format.html do
|
||||||
if authenticated?
|
if authenticated?
|
||||||
@maps = policy_scope(Map).where.not(name: 'Untitled Map')
|
@maps = policy_scope(Map).where.not(name: 'Untitled Map').where.not(permission: 'private')
|
||||||
.order(updated_at: :desc).page(1).per(20)
|
.order(updated_at: :desc).page(1).per(20)
|
||||||
render 'explore/active'
|
render 'explore/active'
|
||||||
else
|
else
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
class MapsController < ApplicationController
|
class MapsController < ApplicationController
|
||||||
before_action :require_user, only: [:create, :update, :destroy, :access, :events]
|
before_action :require_user, only: [:create, :update, :destroy, :events]
|
||||||
before_action :set_map, only: [:show, :update, :destroy, :access, :contains,
|
before_action :set_map, only: [:show, :update, :destroy, :contains, :events, :export]
|
||||||
:events, :export]
|
|
||||||
after_action :verify_authorized
|
after_action :verify_authorized
|
||||||
|
|
||||||
# GET maps/:id
|
# GET maps/:id
|
||||||
|
@ -16,6 +15,7 @@ class MapsController < ApplicationController
|
||||||
@allmappings = policy_scope(@map.mappings)
|
@allmappings = policy_scope(@map.mappings)
|
||||||
@allmessages = @map.messages.sort_by(&:created_at)
|
@allmessages = @map.messages.sort_by(&:created_at)
|
||||||
@allstars = @map.stars
|
@allstars = @map.stars
|
||||||
|
@allrequests = @map.access_requests
|
||||||
end
|
end
|
||||||
format.json { render json: @map }
|
format.json { render json: @map }
|
||||||
format.csv { redirect_to action: :export, format: :csv }
|
format.csv { redirect_to action: :export, format: :csv }
|
||||||
|
@ -80,24 +80,6 @@ class MapsController < ApplicationController
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# POST maps/:id/access
|
|
||||||
def access
|
|
||||||
user_ids = params[:access] || []
|
|
||||||
|
|
||||||
@map.add_new_collaborators(user_ids).each do |user_id|
|
|
||||||
# add_new_collaborators returns array of added users,
|
|
||||||
# who we then send an email to
|
|
||||||
MapMailer.invite_to_edit_email(@map, current_user, User.find(user_id)).deliver_later
|
|
||||||
end
|
|
||||||
@map.remove_old_collaborators(user_ids)
|
|
||||||
|
|
||||||
respond_to do |format|
|
|
||||||
format.json do
|
|
||||||
render json: { message: 'Successfully altered edit permissions' }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# GET maps/:id/contains
|
# GET maps/:id/contains
|
||||||
def contains
|
def contains
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
|
|
|
@ -22,7 +22,7 @@ class TopicsController < ApplicationController
|
||||||
end
|
end
|
||||||
@all= @topics.to_a.concat(@maps.to_a).sort { |a, b| a.name <=> b.name }
|
@all= @topics.to_a.concat(@maps.to_a).sort { |a, b| a.name <=> b.name }
|
||||||
|
|
||||||
render json: autocomplete_array_json(@all)
|
render json: autocomplete_array_json(@all).to_json
|
||||||
end
|
end
|
||||||
|
|
||||||
# GET topics/:id
|
# GET topics/:id
|
||||||
|
|
|
@ -7,6 +7,6 @@ class Users::PasswordsController < Devise::PasswordsController
|
||||||
end
|
end
|
||||||
|
|
||||||
def after_sending_reset_password_instructions_path_for(_resource_name)
|
def after_sending_reset_password_instructions_path_for(_resource_name)
|
||||||
new_user_session_path if is_navigational_format?
|
sign_in_path if is_navigational_format?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,19 +2,34 @@
|
||||||
class Users::RegistrationsController < Devise::RegistrationsController
|
class Users::RegistrationsController < Devise::RegistrationsController
|
||||||
before_action :configure_sign_up_params, only: [:create]
|
before_action :configure_sign_up_params, only: [:create]
|
||||||
before_action :configure_account_update_params, only: [:update]
|
before_action :configure_account_update_params, only: [:update]
|
||||||
|
after_action :store_location, only: [:new]
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
def after_sign_up_path_for(resource)
|
|
||||||
signed_in_root_path(resource)
|
|
||||||
end
|
|
||||||
|
|
||||||
def after_update_path_for(resource)
|
def after_update_path_for(resource)
|
||||||
signed_in_root_path(resource)
|
signed_in_root_path(resource)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def after_sign_in_path_for(resource)
|
||||||
|
stored = stored_location_for(User)
|
||||||
|
return stored if stored
|
||||||
|
|
||||||
|
if request.referer&.match(sign_in_url) || request.referer&.match(sign_up_url)
|
||||||
|
super
|
||||||
|
else
|
||||||
|
request.referer || root_path
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def store_location
|
||||||
|
if params[:redirect_to]
|
||||||
|
store_location_for(User, params[:redirect_to])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def configure_sign_up_params
|
def configure_sign_up_params
|
||||||
devise_parameter_sanitizer.permit(:sign_up, keys: [:name, :joinedwithcode])
|
devise_parameter_sanitizer.permit(:sign_up, keys: [:name, :joinedwithcode])
|
||||||
end
|
end
|
||||||
|
|
2
app/controllers/users/sessions_controller.rb
Normal file
2
app/controllers/users/sessions_controller.rb
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
class Users::SessionsController < Devise::SessionsController
|
||||||
|
end
|
|
@ -2,10 +2,17 @@
|
||||||
class MapMailer < ApplicationMailer
|
class MapMailer < ApplicationMailer
|
||||||
default from: 'team@metamaps.cc'
|
default from: 'team@metamaps.cc'
|
||||||
|
|
||||||
|
def access_request_email(request, map)
|
||||||
|
@request = request
|
||||||
|
@map = map
|
||||||
|
subject = @map.name + ' - request to edit'
|
||||||
|
mail(to: @map.user.email, subject: subject)
|
||||||
|
end
|
||||||
|
|
||||||
def invite_to_edit_email(map, inviter, invitee)
|
def invite_to_edit_email(map, inviter, invitee)
|
||||||
@inviter = inviter
|
@inviter = inviter
|
||||||
@map = map
|
@map = map
|
||||||
subject = @map.name + ' - Invitation to edit'
|
subject = @map.name + ' - invitation to edit'
|
||||||
mail(to: invitee.email, subject: subject)
|
mail(to: invitee.email, subject: subject)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
18
app/models/access_request.rb
Normal file
18
app/models/access_request.rb
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
class AccessRequest < ApplicationRecord
|
||||||
|
belongs_to :user
|
||||||
|
belongs_to :map
|
||||||
|
|
||||||
|
def approve
|
||||||
|
self.approved = true
|
||||||
|
self.answered = true
|
||||||
|
self.save
|
||||||
|
UserMap.create(user: self.user, map: self.map)
|
||||||
|
MapMailer.invite_to_edit_email(self.map, self.map.user, self.user).deliver_later
|
||||||
|
end
|
||||||
|
|
||||||
|
def deny
|
||||||
|
self.approved = false
|
||||||
|
self.answered = true
|
||||||
|
self.save
|
||||||
|
end
|
||||||
|
end
|
|
@ -9,6 +9,7 @@ class Map < ApplicationRecord
|
||||||
has_many :messages, as: :resource, dependent: :destroy
|
has_many :messages, as: :resource, dependent: :destroy
|
||||||
has_many :stars
|
has_many :stars
|
||||||
|
|
||||||
|
has_many :access_requests, dependent: :destroy
|
||||||
has_many :user_maps, dependent: :destroy
|
has_many :user_maps, dependent: :destroy
|
||||||
has_many :collaborators, through: :user_maps, source: :user
|
has_many :collaborators, through: :user_maps, source: :user
|
||||||
|
|
||||||
|
@ -18,10 +19,10 @@ class Map < ApplicationRecord
|
||||||
# This method associates the attribute ":image" with a file attachment
|
# This method associates the attribute ":image" with a file attachment
|
||||||
has_attached_file :screenshot,
|
has_attached_file :screenshot,
|
||||||
styles: {
|
styles: {
|
||||||
thumb: ['188x126#', :png]
|
thumb: ['220x220#', :png]
|
||||||
#:full => ['940x630#', :png]
|
#:full => ['940x630#', :png]
|
||||||
},
|
},
|
||||||
default_url: 'https://s3.amazonaws.com/metamaps-assets/site/missing-map-white.png'
|
default_url: 'https://s3.amazonaws.com/metamaps-assets/site/missing-map-square.png'
|
||||||
|
|
||||||
validates :name, presence: true
|
validates :name, presence: true
|
||||||
validates :arranged, inclusion: { in: [true, false] }
|
validates :arranged, inclusion: { in: [true, false] }
|
||||||
|
@ -58,13 +59,17 @@ class Map < ApplicationRecord
|
||||||
delegate :name, to: :user, prefix: true
|
delegate :name, to: :user, prefix: true
|
||||||
|
|
||||||
def user_image
|
def user_image
|
||||||
user.image.url
|
user.image.url(:thirtytwo)
|
||||||
end
|
end
|
||||||
|
|
||||||
def contributor_count
|
def contributor_count
|
||||||
contributors.length
|
contributors.length
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def star_count
|
||||||
|
stars.length
|
||||||
|
end
|
||||||
|
|
||||||
def collaborator_ids
|
def collaborator_ids
|
||||||
collaborators.map(&:id)
|
collaborators.map(&:id)
|
||||||
end
|
end
|
||||||
|
@ -86,7 +91,7 @@ class Map < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def as_json(_options = {})
|
def as_json(_options = {})
|
||||||
json = super(methods: [:user_name, :user_image, :topic_count, :synapse_count, :contributor_count, :collaborator_ids, :screenshot_url], except: [:screenshot_content_type, :screenshot_file_size, :screenshot_file_name, :screenshot_updated_at])
|
json = super(methods: [:user_name, :user_image, :star_count, :topic_count, :synapse_count, :contributor_count, :collaborator_ids, :screenshot_url], except: [:screenshot_content_type, :screenshot_file_size, :screenshot_file_name, :screenshot_updated_at])
|
||||||
json[:created_at_clean] = created_at_str
|
json[:created_at_clean] = created_at_str
|
||||||
json[:updated_at_clean] = updated_at_str
|
json[:updated_at_clean] = updated_at_str
|
||||||
json
|
json
|
||||||
|
@ -102,7 +107,8 @@ class Map < ApplicationRecord
|
||||||
mappers: contributors,
|
mappers: contributors,
|
||||||
collaborators: editors,
|
collaborators: editors,
|
||||||
messages: messages.sort_by(&:created_at),
|
messages: messages.sort_by(&:created_at),
|
||||||
stars: stars
|
stars: stars,
|
||||||
|
requests: access_requests
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -122,6 +128,7 @@ class Map < ApplicationRecord
|
||||||
removed = current_collaborators.map(&:id).map do |old_user_id|
|
removed = current_collaborators.map(&:id).map do |old_user_id|
|
||||||
next nil if user_ids.include?(old_user_id)
|
next nil if user_ids.include?(old_user_id)
|
||||||
user_maps.where(user_id: old_user_id).find_each(&:destroy)
|
user_maps.where(user_id: old_user_id).find_each(&:destroy)
|
||||||
|
access_requests.where(user_id: old_user_id).find_each(&:destroy)
|
||||||
old_user_id
|
old_user_id
|
||||||
end
|
end
|
||||||
removed.compact
|
removed.compact
|
||||||
|
|
|
@ -65,6 +65,11 @@ class User < ApplicationRecord
|
||||||
json
|
json
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def all_accessible_maps
|
||||||
|
#TODO: is there a way to keep this an ActiveRecord relation?
|
||||||
|
maps + shared_maps
|
||||||
|
end
|
||||||
|
|
||||||
def recentMetacodes
|
def recentMetacodes
|
||||||
array = []
|
array = []
|
||||||
self.topics.sort{|a,b| b.created_at <=> a.created_at }.each do |t|
|
self.topics.sort{|a,b| b.created_at <=> a.created_at }.each do |t|
|
||||||
|
|
|
@ -37,10 +37,36 @@ class MapPolicy < ApplicationPolicy
|
||||||
end
|
end
|
||||||
|
|
||||||
def access?
|
def access?
|
||||||
# note that this is to edit who can access the map
|
# this is for the map creator to bulk change who can access the map
|
||||||
user.present? && record.user == user
|
user.present? && record.user == user
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def request_access?
|
||||||
|
# this is to access the page where you can request access to a map
|
||||||
|
user.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def access_request?
|
||||||
|
# this is to actually request access
|
||||||
|
user.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def approve_access?
|
||||||
|
record.user == user
|
||||||
|
end
|
||||||
|
|
||||||
|
def deny_access?
|
||||||
|
approve_access?
|
||||||
|
end
|
||||||
|
|
||||||
|
def approve_access_post?
|
||||||
|
approve_access?
|
||||||
|
end
|
||||||
|
|
||||||
|
def deny_access_post?
|
||||||
|
approve_access?
|
||||||
|
end
|
||||||
|
|
||||||
def contains?
|
def contains?
|
||||||
show?
|
show?
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,11 +8,13 @@ class MappingPolicy < ApplicationPolicy
|
||||||
# a private topic, since you can't see the private topic anyways
|
# a private topic, since you can't see the private topic anyways
|
||||||
visible = %w(public commons)
|
visible = %w(public commons)
|
||||||
permission = 'maps.permission IN (?)'
|
permission = 'maps.permission IN (?)'
|
||||||
if user
|
return scope.joins(:map).where(permission, visible) unless user
|
||||||
scope.joins(:map).where(permission, visible).or(scope.joins(:map).where(user_id: user.id))
|
|
||||||
else
|
# if this is getting changed, the policy_scope for messages should also be changed
|
||||||
scope.joins(:map).where(permission, visible)
|
# as it is based entirely on the map to which it belongs
|
||||||
end
|
scope.joins(:map).where(permission, visible)
|
||||||
|
.or(scope.joins(:map).where('maps.id IN (?)', user.shared_maps.map(&:id)))
|
||||||
|
.or(scope.joins(:map).where('maps.user_id = ?', user.id))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,13 @@ class MessagePolicy < ApplicationPolicy
|
||||||
def resolve
|
def resolve
|
||||||
visible = %w(public commons)
|
visible = %w(public commons)
|
||||||
permission = 'maps.permission IN (?)'
|
permission = 'maps.permission IN (?)'
|
||||||
if user
|
return scope.joins(:map).where(permission, visible) unless user
|
||||||
scope.joins(:maps).where(permission + ' OR maps.user_id = ?', visible, user.id)
|
|
||||||
else
|
# if this is getting changed, the policy_scope for mappings should also be changed
|
||||||
scope.where(permission, visible)
|
# as it is based entirely on the map to which it belongs
|
||||||
end
|
scope.joins(:map).where(permission, visible)
|
||||||
|
.or(scope.joins(:map).where('maps.id IN (?)', user.shared_maps.map(&:id)))
|
||||||
|
.or(scope.joins(:map).where('maps.user_id = ?', user.id))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -3,11 +3,10 @@ class SynapsePolicy < ApplicationPolicy
|
||||||
class Scope < Scope
|
class Scope < Scope
|
||||||
def resolve
|
def resolve
|
||||||
visible = %w(public commons)
|
visible = %w(public commons)
|
||||||
|
|
||||||
return scope.where(permission: visible) unless user
|
return scope.where(permission: visible) unless user
|
||||||
|
|
||||||
scope.where(permission: visible)
|
scope.where(permission: visible)
|
||||||
.or(scope.where(defer_to_map_id: user.shared_maps.map(&:id)))
|
.or(scope.where.not(defer_to_map_id: nil).where(defer_to_map_id: user.all_accessible_maps.map(&:id)))
|
||||||
.or(scope.where(user_id: user.id))
|
.or(scope.where(user_id: user.id))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,11 +10,11 @@ class TokenPolicy < ApplicationPolicy
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def create?
|
def index?
|
||||||
user.present?
|
user.present?
|
||||||
end
|
end
|
||||||
|
|
||||||
def my_tokens?
|
def create?
|
||||||
user.present?
|
user.present?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ class TopicPolicy < ApplicationPolicy
|
||||||
return scope.where(permission: visible) unless user
|
return scope.where(permission: visible) unless user
|
||||||
|
|
||||||
scope.where(permission: visible)
|
scope.where(permission: visible)
|
||||||
.or(scope.where(defer_to_map_id: user.shared_maps.map(&:id)))
|
.or(scope.where.not(defer_to_map_id: nil).where(defer_to_map_id: user.all_accessible_maps.map(&:id)))
|
||||||
.or(scope.where(user_id: user.id))
|
.or(scope.where(user_id: user.id))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,12 +2,14 @@
|
||||||
<%= render :partial => 'layouts/templates' %>
|
<%= render :partial => 'layouts/templates' %>
|
||||||
<%= render :partial => 'shared/metacodeBgColors' %>
|
<%= render :partial => 'shared/metacodeBgColors' %>
|
||||||
<script type="text/javascript" charset="utf-8">
|
<script type="text/javascript" charset="utf-8">
|
||||||
|
// TODO move this into Metamaps.ServerData somehow
|
||||||
<% if current_user %>
|
<% if current_user %>
|
||||||
Metamaps.Active.Mapper = <%= current_user.to_json.html_safe %>
|
Metamaps.Active.Mapper = <%= current_user.to_json.html_safe %>
|
||||||
<% else %>
|
<% else %>
|
||||||
Metamaps.Active.Mapper = null;
|
Metamaps.Active.Mapper = null;
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
// TODO move this into frontend/
|
||||||
Metamaps.Loading = {
|
Metamaps.Loading = {
|
||||||
loader: new CanvasLoader('loading'),
|
loader: new CanvasLoader('loading'),
|
||||||
hide: function () {
|
hide: function () {
|
||||||
|
@ -22,8 +24,6 @@
|
||||||
Metamaps.Loading.loader.setDensity(41); // default is 40
|
Metamaps.Loading.loader.setDensity(41); // default is 40
|
||||||
Metamaps.Loading.loader.setRange(0.9); // default is 1.3
|
Metamaps.Loading.loader.setRange(0.9); // default is 1.3
|
||||||
Metamaps.Loading.loader.show(); // Hidden by default
|
Metamaps.Loading.loader.show(); // Hidden by default
|
||||||
|
|
||||||
USERVOICE.load();
|
|
||||||
</script>
|
</script>
|
||||||
<%= render :partial => 'layouts/googleanalytics' if Rails.env.production? %>
|
<%= render :partial => 'layouts/googleanalytics' if Rails.env.production? %>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -74,7 +74,7 @@
|
||||||
<a id="chromeIcon" href="https://www.google.com/chrome/browser/" target="_blank">Chrome</a>
|
<a id="chromeIcon" href="https://www.google.com/chrome/browser/" target="_blank">Chrome</a>
|
||||||
<a id="fireFoxIcon" href="https://www.mozilla.org/en-US/firefox/new/" target="_blank">Firefox</a>
|
<a id="fireFoxIcon" href="https://www.mozilla.org/en-US/firefox/new/" target="_blank">Firefox</a>
|
||||||
<a id="safariIcon" href="http://support.apple.com/downloads/#safari" target="_blank">Safari</a>
|
<a id="safariIcon" href="http://support.apple.com/downloads/#safari" target="_blank">Safari</a>
|
||||||
<p id="noIEbody">While it's downloading, explore our <a href="http://blog.metamaps.cc/">blog</a>,<br> watch the <a href="http://vimeo.com/88334167">tutorials</a>, or visit our <a href="http://metamapscc.uservoice.com/">knowledge base</a>!
|
<p id="noIEbody">While it's downloading, explore our <a href="http://blog.metamaps.cc/">blog</a>,<br> watch the <a href="http://vimeo.com/88334167">tutorials</a>, or visit our <a href="https://docs.metamaps.cc">knowledge base</a>!
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -8,11 +8,11 @@
|
||||||
<div class="infoAndHelp">
|
<div class="infoAndHelp">
|
||||||
<%= render :partial => 'maps/mapinfobox' %>
|
<%= render :partial => 'maps/mapinfobox' %>
|
||||||
|
|
||||||
<% starred = current_user && @map && current_user.starred_map?(@map)
|
<% starred = current_user && @map && current_user.starred_map?(@map)
|
||||||
starClass = starred ? 'starred' : ''
|
starClass = starred ? 'starred' : ''
|
||||||
tooltip = starred ? 'Star' : 'Unstar' %>
|
tooltip = starred ? 'Star' : 'Unstar' %>
|
||||||
<div class="starMap infoElement mapElement <%= starClass %>"><div class="tooltipsAbove"><%= tooltip %></div></div>
|
<div class="starMap infoElement mapElement <%= starClass %>"><div class="tooltipsAbove"><%= tooltip %></div></div>
|
||||||
<div class="mapInfoIcon infoElement mapElement"><div class="tooltipsAbove">Map Info</div></div>
|
<div class="mapInfoIcon infoElement mapElement"><div class="tooltipsAbove">Map Info</div></div>
|
||||||
<div class="openCheatsheet openLightbox infoElement mapElement" data-open="cheatsheet"><div class="tooltipsAbove">Help</div></div>
|
<div class="openCheatsheet openLightbox infoElement mapElement" data-open="cheatsheet"><div class="tooltipsAbove">Help</div></div>
|
||||||
<div class="clearfloat"></div>
|
<div class="clearfloat"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
<li>
|
<li>
|
||||||
<%= link_to "Global Maps", explore_active_path, :data => { :router => 'true'} %>
|
<%= link_to "All Maps", explore_active_path, :data => { :router => 'true'} %>
|
||||||
</li>
|
</li>
|
||||||
<% if not current_user %>
|
<% if not current_user %>
|
||||||
<li>
|
<li>
|
||||||
|
@ -42,7 +42,7 @@
|
||||||
<%= link_to "Request Invite", request_path %>
|
<%= link_to "Request Invite", request_path %>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<%= link_to "Login", new_user_session_path %>
|
<%= link_to "Login", sign_in_path %>
|
||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if current_user %>
|
<% if current_user %>
|
||||||
|
|
|
@ -183,11 +183,12 @@
|
||||||
<span class="title">
|
<span class="title">
|
||||||
<div class="titleWrapper" id="titleActivator">
|
<div class="titleWrapper" id="titleActivator">
|
||||||
<span class="best_in_place best_in_place_name"
|
<span class="best_in_place best_in_place_name"
|
||||||
data-url="/topics/{{id}}"
|
data-bip-url="/topics/{{id}}"
|
||||||
data-object="topic"
|
data-bip-object="topic"
|
||||||
data-attribute="name"
|
data-bip-attribute="name"
|
||||||
data-activator="#titleActivator"
|
data-bip-activator="#titleActivator"
|
||||||
data-type="textarea">{{name}}</span>
|
data-bip-value="{{name}}"
|
||||||
|
data-bip-type="textarea">{{name}}</span>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
<div class="links">
|
<div class="links">
|
||||||
|
@ -220,7 +221,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="scroll">
|
<div class="scroll">
|
||||||
<div class="desc">
|
<div class="desc">
|
||||||
<span class="best_in_place best_in_place_desc" data-url="/topics/{{id}}" data-object="topic" data-nil="{{desc_nil}}" data-attribute="desc" data-type="textarea">{{desc}}</span>
|
<span class="best_in_place best_in_place_desc" data-bip-url="/topics/{{id}}" data-bip-object="topic" data-bip-nil="{{desc_nil}}" data-bip-attribute="desc" data-bip-type="textarea" data-bip-value="{{desc_markdown}}">{{{desc_html}}}</span>
|
||||||
<div class="clearfloat"></div>
|
<div class="clearfloat"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -14,11 +14,32 @@
|
||||||
<div class="sidebarSearchIcon"></div>
|
<div class="sidebarSearchIcon"></div>
|
||||||
<div class="clearfloat"></div>
|
<div class="clearfloat"></div>
|
||||||
</div> <!-- end sidebarSearch -->
|
</div> <!-- end sidebarSearch -->
|
||||||
|
|
||||||
|
<% request = current_user && @map && @allrequests.find{|a| a.user == current_user}
|
||||||
|
className = (@map and not policy(@map).update?) ? 'isViewOnly ' : ''
|
||||||
|
if @map
|
||||||
|
className += 'sendRequest' if not request
|
||||||
|
className += 'sentRequest' if request and not request.answered
|
||||||
|
className += 'requestDenied' if request and request.answered and not request.approved
|
||||||
|
end %>
|
||||||
|
|
||||||
|
<div class="viewOnly <%= className %>">
|
||||||
|
<div class="eyeball">View Only</div>
|
||||||
|
<% if current_user %>
|
||||||
|
<div class="requestAccess requestNotice">Request Access</div>
|
||||||
|
<div class="requestPending requestNotice">Request Pending</div>
|
||||||
|
<div class="requestNotAccepted requestNotice">Request Not Accepted</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
<div class="clearfloat"></div>
|
<div class="clearfloat"></div>
|
||||||
</div><!-- end upperLeftUI -->
|
</div><!-- end upperLeftUI -->
|
||||||
|
|
||||||
<div class="upperRightUI">
|
<div class="upperRightUI">
|
||||||
<div class="mapElement upperRightEl upperRightMapButtons">
|
<div class="mapElement upperRightEl upperRightMapButtons">
|
||||||
|
<div class="importDialog infoElement mapElement openLightbox" data-open="import-dialog-lightbox">
|
||||||
|
<div class="tooltipsAbove">Import data</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- filtering -->
|
<!-- filtering -->
|
||||||
<div class="sidebarFilter upperRightEl">
|
<div class="sidebarFilter upperRightEl">
|
||||||
<div class="sidebarFilterIcon upperRightIcon"><div class="tooltipsUnder">Filter</div></div>
|
<div class="sidebarFilterIcon upperRightIcon"><div class="tooltipsUnder">Filter</div></div>
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
<body class="<%= authenticated? ? "authenticated" : "unauthenticated" %>">
|
<body class="<%= authenticated? ? "authenticated" : "unauthenticated" %>">
|
||||||
|
|
||||||
|
<a class='feedback-icon' target='_blank' href='https://hylo.com/c/metamaps'></a>
|
||||||
|
|
||||||
<%= content_tag :div, class: "main" do %>
|
<%= content_tag :div, class: "main" do %>
|
||||||
|
|
||||||
|
|
23
app/views/map_mailer/access_request_email.html.erb
Normal file
23
app/views/map_mailer/access_request_email.html.erb
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta content='text/html; charset=UTF-8' http-equiv='Content-Type' />
|
||||||
|
</head>
|
||||||
|
<body style="font-family: sans-serif; width: 100%; padding: 24px 16px 16px 16px; background-color: #f5f5f5; text-align: center;">
|
||||||
|
|
||||||
|
<div style="padding: 16px; background: white; text-align: left;">
|
||||||
|
<% button_style = "background-color:#4fc059;border-radius:2px;color:white;display:inline-block;font-family:Roboto,Arial,Helvetica,sans-serif;font-size:12px;font-weight:bold;min-height:29px;line-height:29px;min-width:54px;outline:0px;padding:0 8px;text-align:center;text-decoration:none" %>
|
||||||
|
|
||||||
|
<p><span style="font-weight: bold;"><%= @request.user.name %></span> is requesting access to <span style="font-weight: bold">collaboratively edit</span> the following metamap:</p>
|
||||||
|
|
||||||
|
<p><%= @map.name %></p>
|
||||||
|
|
||||||
|
<p><%= link_to "Grant", approve_access_map_url(id: @map.id, request_id: @request.id), target: "_blank", style: "font-size: 18px; text-decoration: none; color: #4fc059;" %>
|
||||||
|
<p><%= link_to "Deny", deny_access_map_url(id: @map.id, request_id: @request.id), target: "_blank", style: "font-size: 18px; text-decoration: none; color: #DB5D5D;" %></p>
|
||||||
|
|
||||||
|
<%= link_to 'Open in Metamaps', map_url(@map), target: "_blank", style: button_style %>
|
||||||
|
|
||||||
|
<p style="font-size: 12px;">Make sense with Metamaps</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
10
app/views/map_mailer/access_request_email.text.erb
Normal file
10
app/views/map_mailer/access_request_email.text.erb
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<%= @request.user.name %> has requested to collaboratively edit the following metamap:
|
||||||
|
|
||||||
|
<%= @map.name %> [<%= map_url(@map) %>]
|
||||||
|
|
||||||
|
Approve Request [<%= approve_access_map_url(id: @map.id, request_id: @request.id) %>]
|
||||||
|
Deny Request [<%= deny_access_map_url(id: @map.id, request_id: @request.id) %>]
|
||||||
|
|
||||||
|
Make sense with Metamaps
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
<div style="padding: 16px; background: white; text-align: left;">
|
<div style="padding: 16px; background: white; text-align: left;">
|
||||||
<% button_style = "background-color:#4fc059;border-radius:2px;color:white;display:inline-block;font-family:Roboto,Arial,Helvetica,sans-serif;font-size:12px;font-weight:bold;min-height:29px;line-height:29px;min-width:54px;outline:0px;padding:0 8px;text-align:center;text-decoration:none" %>
|
<% button_style = "background-color:#4fc059;border-radius:2px;color:white;display:inline-block;font-family:Roboto,Arial,Helvetica,sans-serif;font-size:12px;font-weight:bold;min-height:29px;line-height:29px;min-width:54px;outline:0px;padding:0 8px;text-align:center;text-decoration:none" %>
|
||||||
|
|
||||||
<p><span style="font-weight: bold;"><%= @inviter.name %></span> has invited you to <span style="font-weight: bold">collaboratively edit</span> the following metamap:</p>
|
<p><span style="font-weight: bold;"><%= @inviter.name %></span> has invited you to <span style="font-weight: bold">collaboratively edit</span> the following map:</p>
|
||||||
<p><%= link_to @map.name, map_url(@map), target: "_blank", style: "font-size: 18px; text-decoration: none; color: #4fc059;" %></p>
|
<p><%= link_to @map.name, map_url(@map), target: "_blank", style: "font-size: 18px; text-decoration: none; color: #4fc059;" %></p>
|
||||||
<% if @map.desc %>
|
<% if @map.desc %>
|
||||||
<p style="font-size: 12px;"><%= @map.desc %></p>
|
<p style="font-size: 12px;"><%= @map.desc %></p>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<%= @inviter.name %> has invited you to collaboratively edit the following metamap:
|
<%= @inviter.name %> has invited you to collaboratively edit the following map:
|
||||||
|
|
||||||
<%= @map.name %> [<%= map_url(@map) %>]
|
<%= @map.name %> [<%= map_url(@map) %>]
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
<% if @map %>
|
<% if @map %>
|
||||||
<div class="mapInfoName" id="mapInfoName">
|
<div class="mapInfoName" id="mapInfoName">
|
||||||
<% if policy(@map).update? %>
|
<% if policy(@map).update? %>
|
||||||
<span class="best_in_place best_in_place_name" id="best_in_place_map_<%= @map.id %>_name" data-url="/maps/<%= @map.id %>" data-object="map" data-attribute="name" data-type="textarea" data-activator="#mapInfoName"><%= @map.name %></span>
|
<span class="best_in_place best_in_place_name" id="best_in_place_map_<%= @map.id %>_name" data-bip-url="/maps/<%= @map.id %>" data-bip-object="map" data-bip-attribute="name" data-bip-type="textarea" data-bip-activator="#mapInfoName" data-bip-value="<%= @map.name %>"><%= @map.name %></span>
|
||||||
<% else %>
|
<% else %>
|
||||||
<%= @map.name %>
|
<%= @map.name %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
@ -67,7 +67,7 @@
|
||||||
|
|
||||||
<div class="mapInfoDesc" id="mapInfoDesc">
|
<div class="mapInfoDesc" id="mapInfoDesc">
|
||||||
<% if policy(@map).update? %>
|
<% if policy(@map).update? %>
|
||||||
<span class="best_in_place best_in_place_desc" id="best_in_place_map_<%= @map.id %>_desc" data-url="/maps/<%= @map.id %>" data-object="map" data-attribute="desc" data-nil="Click to add description..." data-type="textarea" data-activator="#mapInfoDesc"><%= @map.desc %></span>
|
<span class="best_in_place best_in_place_desc" id="best_in_place_map_<%= @map.id %>_desc" data-bip-url="/maps/<%= @map.id %>" data-bip-object="map" data-bip-attribute="desc" data-bip-nil="Click to add description..." data-bip-type="textarea" data-bip-activator="#mapInfoDesc" data-bip-value="<%= @map.desc %>"><%= @map.desc %></span>
|
||||||
<% else %>
|
<% else %>
|
||||||
<%= @map.desc %>
|
<%= @map.desc %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
37
app/views/maps/request_access.html.erb
Normal file
37
app/views/maps/request_access.html.erb
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
<%#
|
||||||
|
# @file
|
||||||
|
# Code to request access to a map
|
||||||
|
# /maps/:id/request_access
|
||||||
|
#%>
|
||||||
|
|
||||||
|
<% content_for :title, 'Request Access | Metamaps' %>
|
||||||
|
<% content_for :mobile_title, 'Request Access' %>
|
||||||
|
|
||||||
|
<div id="yield">
|
||||||
|
<div class='request_access'>
|
||||||
|
<div class='monkey'></div>
|
||||||
|
<div class='explainer_text'>
|
||||||
|
Hmmm. This map is private, but you can request to edit it from the map creator.
|
||||||
|
</div>
|
||||||
|
<div class='make_request'>REQUEST ACCESS</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('.make_request').click(function() {
|
||||||
|
var that = $(this)
|
||||||
|
that.off('click')
|
||||||
|
that.text('requesting...')
|
||||||
|
$.ajax({
|
||||||
|
url: '/maps/<%= params[:id] %>/access_request',
|
||||||
|
type: 'POST',
|
||||||
|
contentType: 'application/json',
|
||||||
|
statusCode: {
|
||||||
|
200: function () { that.text('Request Sent'); setTimeout(function () {window.location.href = '/'}, 2000) },
|
||||||
|
400: function () { that.text('An error occurred') }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
</script>
|
|
@ -39,7 +39,7 @@
|
||||||
<div class="csItem indented"><span class="csTitle">Esc:</span> Hides auto-suggestion results</div>
|
<div class="csItem indented"><span class="csTitle">Esc:</span> Hides auto-suggestion results</div>
|
||||||
<div class="csItem indented"><span class="csTitle">Enter:</span> create a new topic</div>
|
<div class="csItem indented"><span class="csTitle">Enter:</span> create a new topic</div>
|
||||||
<div class="csItem indented"><span class="csTitle">Gear Icon:</span> open up metacode settings</div>
|
<div class="csItem indented"><span class="csTitle">Gear Icon:</span> open up metacode settings</div>
|
||||||
<div class="csItem"><br><a href="http://metamapscc.uservoice.com/knowledgebase/articles/425787-creating-and-editing-topics" target= "_blank">Learn More</a></div>
|
<div class="csItem"><br><a href="https://docs.metamaps.cc/creating_topics.html" target= "_blank">Learn More</a></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="csEditingTopics">
|
<div id="csEditingTopics">
|
||||||
|
@ -71,7 +71,7 @@
|
||||||
<span class="csTitle">Open 'Context Menu':</span> Right-click/alt+click on topic icon or synapse
|
<span class="csTitle">Open 'Context Menu':</span> Right-click/alt+click on topic icon or synapse
|
||||||
</div>
|
</div>
|
||||||
<div class="csItem indented">*Hide/Remove/Delete topic within context menu</div>
|
<div class="csItem indented">*Hide/Remove/Delete topic within context menu</div>
|
||||||
<div class="csItem"><br><a href="http://metamapscc.uservoice.com/knowledgebase/articles/425787-creating-and-editing-topics" target= "_blank">Learn More</a></div>
|
<div class="csItem"><br><a href="https://docs.metamaps.cc/creating_topics.html" target= "_blank">Learn More</a></div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -83,7 +83,7 @@
|
||||||
<div class="csItem"><span class="csTitle">Create new Topic with Synapse:</span> Right-click + drag from topic to open canvas</div>
|
<div class="csItem"><span class="csTitle">Create new Topic with Synapse:</span> Right-click + drag from topic to open canvas</div>
|
||||||
<div class="csItem indented"><span class="csTitle">Enter:</span> Create topic</div>
|
<div class="csItem indented"><span class="csTitle">Enter:</span> Create topic</div>
|
||||||
<div class="csItem indented"><span class="csTitle">Enter:</span> Create synapse</div>
|
<div class="csItem indented"><span class="csTitle">Enter:</span> Create synapse</div>
|
||||||
<div class="csItem"><br><a href="http://metamapscc.uservoice.com/knowledgebase/articles/425790-creating-and-editing-synapses" target= "_blank">Learn More</a></div>
|
<div class="csItem"><br><a href="https://docs.metamaps.cc/creating_synapses.html" target= "_blank">Learn More</a></div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -96,7 +96,7 @@
|
||||||
<div class="csItem indented"><span class="csTitle">Browse synapses / change visible synapse</span> click on arrow icon and select desired synapse</div>
|
<div class="csItem indented"><span class="csTitle">Browse synapses / change visible synapse</span> click on arrow icon and select desired synapse</div>
|
||||||
<div class="csItem"><span class="csTitle">Open 'Context Menu':</span> Right-click/alt-click on Synapse</div>
|
<div class="csItem"><span class="csTitle">Open 'Context Menu':</span> Right-click/alt-click on Synapse</div>
|
||||||
<div class="csItem indented">*Hide/Remove/Delete synapse within context menu</div>
|
<div class="csItem indented">*Hide/Remove/Delete synapse within context menu</div>
|
||||||
<div class="csItem"><br><a href="http://metamapscc.uservoice.com/knowledgebase/articles/425790-creating-and-editing-synapses" target= "_blank">Learn More</a></div>
|
<div class="csItem"><br><a href="https://docs.metamaps.cc/creating_synapses.html" target= "_blank">Learn More</a></div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -104,7 +104,7 @@
|
||||||
<div class="csItem"><span class="csTitle">Move around Canvas:</span> Click and drag</div>
|
<div class="csItem"><span class="csTitle">Move around Canvas:</span> Click and drag</div>
|
||||||
<div class="csItem"><span class="csTitle">Zoom in/out:</span> Scroll OR click on <div id="zoomIn"> </div> & <div id="zoomOut"> </div></div>
|
<div class="csItem"><span class="csTitle">Zoom in/out:</span> Scroll OR click on <div id="zoomIn"> </div> & <div id="zoomOut"> </div></div>
|
||||||
<div class="csItem"><span class="csTitle">Zoom to see all:</span> Click <div id="centerMap"></div> OR Ctrl + E</div>
|
<div class="csItem"><span class="csTitle">Zoom to see all:</span> Click <div id="centerMap"></div> OR Ctrl + E</div>
|
||||||
<div class="csItem"><br><a href="http://metamapscc.uservoice.com/knowledgebase/articles/425784-viewing-existing-maps" target= "_blank">Learn More</a></div>
|
<div class="csItem"><br><a href="https://docs.metamaps.cc/exploring_maps.html" target= "_blank">Learn More</a></div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -153,43 +153,21 @@
|
||||||
<div id="moreResources">
|
<div id="moreResources">
|
||||||
<p>For more information about Metamaps.cc, visit our Knowledge Base or skip directly to a section by clicking on one of the categories below.</p>
|
<p>For more information about Metamaps.cc, visit our Knowledge Base or skip directly to a section by clicking on one of the categories below.</p>
|
||||||
<div class="resourcesColumnOne resourcesColumn">
|
<div class="resourcesColumnOne resourcesColumn">
|
||||||
<a href="http://metamapscc.uservoice.com/forums/262715-general" target="_blank" class="button">Feedback Forums</a>
|
<a href="https://hylo.com/c/metamaps" target="_blank" class="button">Hylo User Community</a>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li><a href="https://docs.metamaps.cc/getting_started.html" target="_blank">Getting Started</a></li>
|
||||||
<a href="http://metamapscc.uservoice.com/knowledgebase/topics/61031-getting-started" target="_blank">Getting Started</a>
|
<li><a href="https://docs.metamaps.cc/best_practices.html" target="_blank">Best Practices</a></li>
|
||||||
</li>
|
<li><a href="https://docs.metamaps.cc/applications_and_use_cases.html" target="_blank">Applications & Use Cases</a></li>
|
||||||
<li>
|
<li><a href="https://docs.metamaps.cc/advanced_features.html" target="_blank">Advanced Features</a></li>
|
||||||
<a href="http://metamapscc.uservoice.com/knowledgebase/topics/63372-key-fundamentals" target="_blank">Key Fundamentals</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="http://metamapscc.uservoice.com/knowledgebase/topics/61033-best-practices" target="_blank">Best Practices</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="http://metamapscc.uservoice.com/knowledgebase/topics/63377-general-troubleshooting" target="_blank">General Troubleshooting</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="http://metamapscc.uservoice.com/knowledgebase/topics/63443-applications-use-cases" target="_blank">Applications & Use Cases</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="resourcesColumnTwo resourcesColumn">
|
<div class="resourcesColumnTwo resourcesColumn">
|
||||||
<a href="http://metamapscc.uservoice.com/knowledgebase" target="_blank" class="button">KNOWLEDGE BASE</a>
|
<a href="https://docs.metamaps.cc" target="_blank" class="button">KNOWLEDGE BASE</a>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li><a href="https://docs.metamaps.cc/general_questions.html" target="_blank">General Questions</a></li>
|
||||||
<a href="http://metamapscc.uservoice.com/knowledgebase/topics/63440-general-questions" target="_blank">General Questions</a>
|
<li><a href="https://docs.metamaps.cc/project_organization_and_governance.html" target="_blank">Organization & Governance</a></li>
|
||||||
</li>
|
<li><a href="https://docs.metamaps.cc/realtime_collaboration_junto.html" target="_blank">Realtime Collaboration</a></li>
|
||||||
<li>
|
<li><a href="https://docs.metamaps.cc/importing_and_exporting_data.html" target="_blank">Importing and Exporting Data</a></li>
|
||||||
<a href="http://metamapscc.uservoice.com/knowledgebase/topics/63375-getting-involved" target="_blank">Getting Involved</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="http://metamapscc.uservoice.com/knowledgebase/topics/63376-project-organization-governance" target="_blank">Organization & Governance</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="http://metamapscc.uservoice.com/knowledgebase/topics/63378-technical-infrastructure" target="_blank">Technical Infrastructure</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="http://metamapscc.uservoice.com/knowledgebase/topics/63587-theory-references" target="_blank">References & Key Theory</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,8 +2,16 @@
|
||||||
|
|
||||||
# Note: you need to run `npm install` before using this script or raml2html won't be installed
|
# Note: you need to run `npm install` before using this script or raml2html won't be installed
|
||||||
|
|
||||||
|
OLD_DIR=$(pwd)
|
||||||
|
cd $(dirname $0)/..
|
||||||
|
|
||||||
if [[ ! -x ./node_modules/.bin/raml2html ]]; then
|
if [[ ! -x ./node_modules/.bin/raml2html ]]; then
|
||||||
npm install
|
npm install
|
||||||
fi
|
fi
|
||||||
|
|
||||||
./node_modules/.bin/raml2html -i ./doc/api/api.raml -o ./public/api/index.html
|
./node_modules/.bin/raml2html -i ./doc/api/api.raml -o ./public/api/index.html -t doc/api/templates/template.nunjucks
|
||||||
|
if [[ -x $(which open) ]]; then
|
||||||
|
open public/api/index.html
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd $OLD_DIR
|
||||||
|
|
3
bin/bundle
Executable file
3
bin/bundle
Executable file
|
@ -0,0 +1,3 @@
|
||||||
|
#!/usr/bin/env ruby
|
||||||
|
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
|
||||||
|
load Gem.bin_path('bundler', 'bundle')
|
4
bin/rails
Executable file
4
bin/rails
Executable file
|
@ -0,0 +1,4 @@
|
||||||
|
#!/usr/bin/env ruby
|
||||||
|
APP_PATH = File.expand_path('../config/application', __dir__)
|
||||||
|
require_relative '../config/boot'
|
||||||
|
require 'rails/commands'
|
4
bin/rake
Executable file
4
bin/rake
Executable file
|
@ -0,0 +1,4 @@
|
||||||
|
#!/usr/bin/env ruby
|
||||||
|
require_relative '../config/boot'
|
||||||
|
require 'rake'
|
||||||
|
Rake.application.run
|
|
@ -32,7 +32,7 @@ Rails.application.configure do
|
||||||
# Print deprecation notices to the Rails logger
|
# Print deprecation notices to the Rails logger
|
||||||
config.active_support.deprecation = :log
|
config.active_support.deprecation = :log
|
||||||
|
|
||||||
config.action_mailer.preview_path = '/vagrant/spec/mailers/previews'
|
config.action_mailer.preview_path = "#{Rails.root}/spec/mailers/previews"
|
||||||
|
|
||||||
# Expands the lines which load the assets
|
# Expands the lines which load the assets
|
||||||
config.assets.debug = false
|
config.assets.debug = false
|
||||||
|
|
|
@ -30,6 +30,7 @@ Metamaps::Application.configure do
|
||||||
# The :test delivery method accumulates sent emails in the
|
# The :test delivery method accumulates sent emails in the
|
||||||
# ActionMailer::Base.deliveries array.
|
# ActionMailer::Base.deliveries array.
|
||||||
config.action_mailer.delivery_method = :test
|
config.action_mailer.delivery_method = :test
|
||||||
|
config.action_mailer.default_url_options = { host: 'localhost:3000' }
|
||||||
|
|
||||||
# Print deprecation notices to the stderr
|
# Print deprecation notices to the stderr
|
||||||
config.active_support.deprecation = :stderr
|
config.active_support.deprecation = :stderr
|
||||||
|
|
|
@ -5,13 +5,13 @@ Doorkeeper.configure do
|
||||||
|
|
||||||
# This block will be called to check whether the resource owner is authenticated or not.
|
# This block will be called to check whether the resource owner is authenticated or not.
|
||||||
resource_owner_authenticator do
|
resource_owner_authenticator do
|
||||||
current_user || redirect_to(new_user_session_url)
|
current_user || redirect_to(sign_in_url)
|
||||||
end
|
end
|
||||||
|
|
||||||
# If you want to restrict access to the web interface for adding oauth authorized applications,
|
# If you want to restrict access to the web interface for adding oauth authorized applications,
|
||||||
# you need to declare the block below.
|
# you need to declare the block below.
|
||||||
admin_authenticator do
|
admin_authenticator do
|
||||||
current_user || redirect_to(new_user_session_url)
|
current_user || redirect_to(sign_in_url)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Authorization Code expiration time (default 10 minutes).
|
# Authorization Code expiration time (default 10 minutes).
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
require 'uservoice-ruby'
|
|
||||||
|
|
||||||
def current_sso_token
|
|
||||||
@current_sso_token ||= UserVoice.generate_sso_token(
|
|
||||||
'metamapscc',
|
|
||||||
ENV['SSO_KEY'],
|
|
||||||
{ email: current_user.email },
|
|
||||||
300 # Default expiry time is 5 minutes = 300 seconds
|
|
||||||
)
|
|
||||||
end
|
|
|
@ -19,9 +19,17 @@ Metamaps::Application.routes.draw do
|
||||||
get :export
|
get :export
|
||||||
post 'events/:event', action: :events
|
post 'events/:event', action: :events
|
||||||
get :contains
|
get :contains
|
||||||
post :access, default: { format: :json }
|
|
||||||
post :star, to: 'stars#create', defaults: { format: :json }
|
get :request_access, to: 'access#request_access'
|
||||||
post :unstar, to: 'stars#destroy', defaults: { format: :json }
|
get 'approve_access/:request_id', to: 'access#approve_access', as: :approve_access
|
||||||
|
get 'deny_access/:request_id', to: 'access#deny_access', as: :deny_access
|
||||||
|
post :access_request, to: 'access#access_request', default: { format: :json }
|
||||||
|
post 'approve_access/:request_id', to: 'access#approve_access_post', default: { format: :json }
|
||||||
|
post 'deny_access/:request_id', to: 'access#deny_access_post', default: { format: :json }
|
||||||
|
post :access, to: 'access#access', default: { format: :json }
|
||||||
|
|
||||||
|
post :star, to: 'stars#create', default: { format: :json }
|
||||||
|
post :unstar, to: 'stars#destroy', default: { format: :json }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -54,6 +62,19 @@ Metamaps::Application.routes.draw do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
devise_for :users, skip: :sessions, controllers: {
|
||||||
|
registrations: 'users/registrations',
|
||||||
|
passwords: 'users/passwords',
|
||||||
|
sessions: 'users/sessions'
|
||||||
|
}
|
||||||
|
|
||||||
|
devise_scope :user do
|
||||||
|
get 'login' => 'users/sessions#new', :as => :sign_in
|
||||||
|
post 'login' => 'users/sessions#create', :as => :user_session
|
||||||
|
get 'logout' => 'users/sessions#destroy', :as => :destroy_user_session
|
||||||
|
get 'join' => 'users/registrations#new', :as => :sign_up
|
||||||
|
end
|
||||||
|
|
||||||
resources :users, except: [:index, :destroy] do
|
resources :users, except: [:index, :destroy] do
|
||||||
member do
|
member do
|
||||||
get :details
|
get :details
|
||||||
|
@ -70,38 +91,18 @@ Metamaps::Application.routes.draw do
|
||||||
delete :stars, to: 'stars#destroy', on: :member
|
delete :stars, to: 'stars#destroy', on: :member
|
||||||
end
|
end
|
||||||
resources :synapses, only: [:index, :create, :show, :update, :destroy]
|
resources :synapses, only: [:index, :create, :show, :update, :destroy]
|
||||||
resources :tokens, only: [:create, :destroy] do
|
resources :tokens, only: [:index, :create, :destroy]
|
||||||
get :my_tokens, on: :collection
|
|
||||||
end
|
|
||||||
resources :topics, only: [:index, :create, :show, :update, :destroy]
|
resources :topics, only: [:index, :create, :show, :update, :destroy]
|
||||||
resources :users, only: [:index, :show] do
|
resources :users, only: [:index, :show] do
|
||||||
get :current, on: :collection
|
get :current, on: :collection
|
||||||
end
|
end
|
||||||
|
match '*path', to: 'restful#catch_404', via: :all
|
||||||
end
|
end
|
||||||
namespace :v1, path: '/v1' do
|
namespace :v1, path: '/v1' do
|
||||||
# api v1 routes all lead to a deprecation error method
|
root to: 'deprecated#deprecated', via: :all
|
||||||
# see app/controllers/api/v1/deprecated_controller.rb
|
match '*path', to: 'deprecated#deprecated', via: :all
|
||||||
resources :maps, only: [:create, :show, :update, :destroy]
|
|
||||||
resources :synapses, only: [:create, :show, :update, :destroy]
|
|
||||||
resources :topics, only: [:create, :show, :update, :destroy]
|
|
||||||
resources :mappings, only: [:create, :show, :update, :destroy]
|
|
||||||
resources :tokens, only: [:create, :destroy] do
|
|
||||||
get :my_tokens, on: :collection
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
match '*path', to: 'v2/restful#catch_404', via: :all
|
||||||
|
|
||||||
devise_for :users, skip: :sessions, controllers: {
|
|
||||||
registrations: 'users/registrations',
|
|
||||||
passwords: 'users/passwords',
|
|
||||||
sessions: 'devise/sessions'
|
|
||||||
}
|
|
||||||
|
|
||||||
devise_scope :user do
|
|
||||||
get 'login' => 'devise/sessions#new', :as => :new_user_session
|
|
||||||
post 'login' => 'devise/sessions#create', :as => :user_session
|
|
||||||
get 'logout' => 'devise/sessions#destroy', :as => :destroy_user_session
|
|
||||||
get 'join' => 'devise/registrations#new', :as => :new_user_registration_path
|
|
||||||
end
|
end
|
||||||
|
|
||||||
namespace :hacks do
|
namespace :hacks do
|
||||||
|
|
12
db/migrate/20161013162214_create_access_requests.rb
Normal file
12
db/migrate/20161013162214_create_access_requests.rb
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
class CreateAccessRequests < ActiveRecord::Migration[5.0]
|
||||||
|
def change
|
||||||
|
create_table :access_requests do |t|
|
||||||
|
t.references :user, foreign_key: true
|
||||||
|
t.boolean :approved, default: false
|
||||||
|
t.boolean :answered, default: false
|
||||||
|
t.references :map, foreign_key: true
|
||||||
|
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
93
db/schema.rb
93
db/schema.rb
|
@ -10,11 +10,22 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(version: 20160928022635) do
|
ActiveRecord::Schema.define(version: 20161013162214) do
|
||||||
|
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
|
||||||
|
create_table "access_requests", force: :cascade do |t|
|
||||||
|
t.integer "user_id"
|
||||||
|
t.boolean "approved", default: false
|
||||||
|
t.boolean "answered", default: false
|
||||||
|
t.integer "map_id"
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
t.index ["map_id"], name: "index_access_requests_on_map_id", using: :btree
|
||||||
|
t.index ["user_id"], name: "index_access_requests_on_user_id", using: :btree
|
||||||
|
end
|
||||||
|
|
||||||
create_table "delayed_jobs", force: :cascade do |t|
|
create_table "delayed_jobs", force: :cascade do |t|
|
||||||
t.integer "priority", default: 0, null: false
|
t.integer "priority", default: 0, null: false
|
||||||
t.integer "attempts", default: 0, null: false
|
t.integer "attempts", default: 0, null: false
|
||||||
|
@ -36,13 +47,10 @@ ActiveRecord::Schema.define(version: 20160928022635) do
|
||||||
t.string "eventable_type"
|
t.string "eventable_type"
|
||||||
t.integer "user_id"
|
t.integer "user_id"
|
||||||
t.integer "map_id"
|
t.integer "map_id"
|
||||||
t.integer "sequence_id"
|
|
||||||
t.datetime "created_at"
|
t.datetime "created_at"
|
||||||
t.datetime "updated_at"
|
t.datetime "updated_at"
|
||||||
t.index ["eventable_type", "eventable_id"], name: "index_events_on_eventable_type_and_eventable_id", using: :btree
|
t.index ["eventable_type", "eventable_id"], name: "index_events_on_eventable_type_and_eventable_id", using: :btree
|
||||||
t.index ["map_id", "sequence_id"], name: "index_events_on_map_id_and_sequence_id", unique: true, using: :btree
|
|
||||||
t.index ["map_id"], name: "index_events_on_map_id", using: :btree
|
t.index ["map_id"], name: "index_events_on_map_id", using: :btree
|
||||||
t.index ["sequence_id"], name: "index_events_on_sequence_id", using: :btree
|
|
||||||
t.index ["user_id"], name: "index_events_on_user_id", using: :btree
|
t.index ["user_id"], name: "index_events_on_user_id", using: :btree
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -67,6 +75,7 @@ ActiveRecord::Schema.define(version: 20160928022635) do
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
t.integer "mappable_id"
|
t.integer "mappable_id"
|
||||||
t.string "mappable_type"
|
t.string "mappable_type"
|
||||||
|
t.boolean "in_trash"
|
||||||
t.index ["map_id", "synapse_id"], name: "index_mappings_on_map_id_and_synapse_id", using: :btree
|
t.index ["map_id", "synapse_id"], name: "index_mappings_on_map_id_and_synapse_id", using: :btree
|
||||||
t.index ["map_id", "topic_id"], name: "index_mappings_on_map_id_and_topic_id", using: :btree
|
t.index ["map_id", "topic_id"], name: "index_mappings_on_map_id_and_topic_id", using: :btree
|
||||||
t.index ["map_id"], name: "index_mappings_on_map_id", using: :btree
|
t.index ["map_id"], name: "index_mappings_on_map_id", using: :btree
|
||||||
|
@ -75,16 +84,16 @@ ActiveRecord::Schema.define(version: 20160928022635) do
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "maps", force: :cascade do |t|
|
create_table "maps", force: :cascade do |t|
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
t.text "name"
|
t.text "name"
|
||||||
t.boolean "arranged"
|
|
||||||
t.text "desc"
|
t.text "desc"
|
||||||
t.text "permission"
|
t.text "permission"
|
||||||
t.integer "user_id"
|
t.integer "user_id"
|
||||||
t.datetime "created_at", null: false
|
t.boolean "arranged"
|
||||||
t.datetime "updated_at", null: false
|
|
||||||
t.boolean "featured"
|
t.boolean "featured"
|
||||||
t.string "screenshot_file_name"
|
t.string "screenshot_file_name", limit: 255
|
||||||
t.string "screenshot_content_type"
|
t.string "screenshot_content_type", limit: 255
|
||||||
t.integer "screenshot_file_size"
|
t.integer "screenshot_file_size"
|
||||||
t.datetime "screenshot_updated_at"
|
t.datetime "screenshot_updated_at"
|
||||||
t.index ["user_id"], name: "index_maps_on_user_id", using: :btree
|
t.index ["user_id"], name: "index_maps_on_user_id", using: :btree
|
||||||
|
@ -103,21 +112,21 @@ ActiveRecord::Schema.define(version: 20160928022635) do
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "metacode_sets", force: :cascade do |t|
|
create_table "metacode_sets", force: :cascade do |t|
|
||||||
t.string "name"
|
t.string "name", limit: 255
|
||||||
t.text "desc"
|
t.text "desc"
|
||||||
t.integer "user_id"
|
t.integer "user_id"
|
||||||
t.boolean "mapperContributed"
|
t.boolean "mapperContributed"
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
t.index ["user_id"], name: "index_metacode_sets_on_user_id", using: :btree
|
t.index ["user_id"], name: "index_metacode_sets_on_user_id", using: :btree
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "metacodes", force: :cascade do |t|
|
create_table "metacodes", force: :cascade do |t|
|
||||||
t.text "name"
|
t.text "name"
|
||||||
t.string "manual_icon"
|
t.string "manual_icon", limit: 255
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
t.string "color"
|
t.string "color", limit: 255
|
||||||
t.string "aws_icon_file_name"
|
t.string "aws_icon_file_name"
|
||||||
t.string "aws_icon_content_type"
|
t.string "aws_icon_content_type"
|
||||||
t.integer "aws_icon_file_size"
|
t.integer "aws_icon_file_size"
|
||||||
|
@ -173,14 +182,15 @@ ActiveRecord::Schema.define(version: 20160928022635) do
|
||||||
create_table "synapses", force: :cascade do |t|
|
create_table "synapses", force: :cascade do |t|
|
||||||
t.text "desc"
|
t.text "desc"
|
||||||
t.text "category"
|
t.text "category"
|
||||||
t.text "weight"
|
|
||||||
t.text "permission"
|
|
||||||
t.integer "topic1_id"
|
t.integer "topic1_id"
|
||||||
t.integer "topic2_id"
|
t.integer "topic2_id"
|
||||||
t.integer "user_id"
|
t.integer "user_id"
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
|
t.text "permission"
|
||||||
|
t.text "weight"
|
||||||
t.integer "defer_to_map_id"
|
t.integer "defer_to_map_id"
|
||||||
|
t.boolean "in_trash"
|
||||||
t.index ["topic1_id", "topic1_id"], name: "index_synapses_on_node1_id_and_node1_id", using: :btree
|
t.index ["topic1_id", "topic1_id"], name: "index_synapses_on_node1_id_and_node1_id", using: :btree
|
||||||
t.index ["topic1_id"], name: "index_synapses_on_topic1_id", using: :btree
|
t.index ["topic1_id"], name: "index_synapses_on_topic1_id", using: :btree
|
||||||
t.index ["topic2_id", "topic2_id"], name: "index_synapses_on_node2_id_and_node2_id", using: :btree
|
t.index ["topic2_id", "topic2_id"], name: "index_synapses_on_node2_id_and_node2_id", using: :btree
|
||||||
|
@ -201,20 +211,21 @@ ActiveRecord::Schema.define(version: 20160928022635) do
|
||||||
t.text "name"
|
t.text "name"
|
||||||
t.text "desc"
|
t.text "desc"
|
||||||
t.text "link"
|
t.text "link"
|
||||||
t.text "permission"
|
|
||||||
t.integer "user_id"
|
t.integer "user_id"
|
||||||
t.integer "metacode_id"
|
t.integer "metacode_id"
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
t.string "image_file_name"
|
t.text "permission"
|
||||||
t.string "image_content_type"
|
t.string "image_file_name", limit: 255
|
||||||
|
t.string "image_content_type", limit: 255
|
||||||
t.integer "image_file_size"
|
t.integer "image_file_size"
|
||||||
t.datetime "image_updated_at"
|
t.datetime "image_updated_at"
|
||||||
t.string "audio_file_name"
|
t.string "audio_file_name", limit: 255
|
||||||
t.string "audio_content_type"
|
t.string "audio_content_type", limit: 255
|
||||||
t.integer "audio_file_size"
|
t.integer "audio_file_size"
|
||||||
t.datetime "audio_updated_at"
|
t.datetime "audio_updated_at"
|
||||||
t.integer "defer_to_map_id"
|
t.integer "defer_to_map_id"
|
||||||
|
t.boolean "in_trash"
|
||||||
t.index ["metacode_id"], name: "index_topics_on_metacode_id", using: :btree
|
t.index ["metacode_id"], name: "index_topics_on_metacode_id", using: :btree
|
||||||
t.index ["user_id"], name: "index_topics_on_user_id", using: :btree
|
t.index ["user_id"], name: "index_topics_on_user_id", using: :btree
|
||||||
end
|
end
|
||||||
|
@ -229,30 +240,30 @@ ActiveRecord::Schema.define(version: 20160928022635) do
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "users", force: :cascade do |t|
|
create_table "users", force: :cascade do |t|
|
||||||
t.string "name"
|
t.string "name", limit: 255
|
||||||
t.string "email"
|
t.string "email", limit: 255
|
||||||
t.text "settings"
|
t.string "crypted_password", limit: 255
|
||||||
t.string "code", limit: 8
|
t.string "password_salt", limit: 255
|
||||||
t.string "joinedwithcode", limit: 8
|
t.string "persistence_token", limit: 255
|
||||||
t.string "crypted_password"
|
t.string "perishable_token", limit: 255
|
||||||
t.string "password_salt"
|
|
||||||
t.string "persistence_token"
|
|
||||||
t.string "perishable_token"
|
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
|
t.string "code", limit: 8
|
||||||
|
t.string "joinedwithcode", limit: 8
|
||||||
|
t.text "settings"
|
||||||
t.string "encrypted_password", limit: 128, default: ""
|
t.string "encrypted_password", limit: 128, default: ""
|
||||||
t.string "remember_token"
|
t.string "remember_token", limit: 255
|
||||||
t.datetime "remember_created_at"
|
t.datetime "remember_created_at"
|
||||||
t.string "reset_password_token"
|
t.string "reset_password_token", limit: 255
|
||||||
t.datetime "last_sign_in_at"
|
t.datetime "last_sign_in_at"
|
||||||
t.string "last_sign_in_ip"
|
t.string "last_sign_in_ip", limit: 255
|
||||||
t.integer "sign_in_count", default: 0
|
t.integer "sign_in_count", default: 0
|
||||||
t.datetime "current_sign_in_at"
|
t.datetime "current_sign_in_at"
|
||||||
t.string "current_sign_in_ip"
|
t.string "current_sign_in_ip", limit: 255
|
||||||
t.datetime "reset_password_sent_at"
|
t.datetime "reset_password_sent_at"
|
||||||
t.boolean "admin"
|
t.boolean "admin"
|
||||||
t.string "image_file_name"
|
t.string "image_file_name", limit: 255
|
||||||
t.string "image_content_type"
|
t.string "image_content_type", limit: 255
|
||||||
t.integer "image_file_size"
|
t.integer "image_file_size"
|
||||||
t.datetime "image_updated_at"
|
t.datetime "image_updated_at"
|
||||||
t.integer "generation"
|
t.integer "generation"
|
||||||
|
@ -268,5 +279,7 @@ ActiveRecord::Schema.define(version: 20160928022635) do
|
||||||
t.index ["hookable_type", "hookable_id"], name: "index_webhooks_on_hookable_type_and_hookable_id", using: :btree
|
t.index ["hookable_type", "hookable_id"], name: "index_webhooks_on_hookable_type_and_hookable_id", using: :btree
|
||||||
end
|
end
|
||||||
|
|
||||||
|
add_foreign_key "access_requests", "maps"
|
||||||
|
add_foreign_key "access_requests", "users"
|
||||||
add_foreign_key "tokens", "users"
|
add_foreign_key "tokens", "users"
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,13 +1,19 @@
|
||||||
#%RAML 1.0
|
#%RAML 1.0
|
||||||
---
|
---
|
||||||
title: Metamaps
|
title: Metamaps
|
||||||
version: v2.0
|
version: 2.0
|
||||||
baseUri: https://metamaps.cc/api/v2
|
baseUri: https://metamaps.cc/api/v2
|
||||||
mediaType: application/json
|
mediaType: application/json
|
||||||
|
protocols: [ HTTPS ]
|
||||||
|
documentation:
|
||||||
|
- title: Getting Started
|
||||||
|
content: !include pages/getting-started.md
|
||||||
|
|
||||||
securitySchemes:
|
securitySchemes:
|
||||||
|
cookie: !include securitySchemes/cookie.raml
|
||||||
|
token: !include securitySchemes/token.raml
|
||||||
oauth_2_0: !include securitySchemes/oauth_2_0.raml
|
oauth_2_0: !include securitySchemes/oauth_2_0.raml
|
||||||
securedBy: [ oauth_2_0 ]
|
securedBy: [ cookie, token, oauth_2_0 ]
|
||||||
|
|
||||||
traits:
|
traits:
|
||||||
pageable: !include traits/pageable.raml
|
pageable: !include traits/pageable.raml
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#type: collection
|
#type: collection
|
||||||
|
securedBy: [ null, cookie, token, oauth_2_0 ]
|
||||||
get:
|
get:
|
||||||
is: [ searchable: { searchFields: "name" }, orderable, pageable ]
|
is: [ searchable: { searchFields: "name" }, orderable, pageable ]
|
||||||
responses:
|
responses:
|
||||||
|
@ -7,6 +8,7 @@ get:
|
||||||
application/json:
|
application/json:
|
||||||
example: !include ../examples/metacodes.json
|
example: !include ../examples/metacodes.json
|
||||||
/{id}:
|
/{id}:
|
||||||
|
securedBy: [ null, cookie, token, oauth_2_0 ]
|
||||||
#type: item
|
#type: item
|
||||||
get:
|
get:
|
||||||
responses:
|
responses:
|
||||||
|
|
|
@ -1,4 +1,13 @@
|
||||||
#type: collection
|
#type: collection
|
||||||
|
get:
|
||||||
|
description: |
|
||||||
|
A list of the current user's tokens.
|
||||||
|
is: [ searchable: { searchFields: description }, pageable, orderable ]
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
body:
|
||||||
|
application/json:
|
||||||
|
example: !include ../examples/tokens.json
|
||||||
post:
|
post:
|
||||||
body:
|
body:
|
||||||
application/json:
|
application/json:
|
||||||
|
@ -11,14 +20,6 @@ post:
|
||||||
body:
|
body:
|
||||||
application/json:
|
application/json:
|
||||||
example: !include ../examples/token.json
|
example: !include ../examples/token.json
|
||||||
/my_tokens:
|
|
||||||
get:
|
|
||||||
is: [ searchable: { searchFields: description }, pageable, orderable ]
|
|
||||||
responses:
|
|
||||||
200:
|
|
||||||
body:
|
|
||||||
application/json:
|
|
||||||
example: !include ../examples/tokens.json
|
|
||||||
/{id}:
|
/{id}:
|
||||||
#type: item
|
#type: item
|
||||||
delete:
|
delete:
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#type: collection
|
#type: collection
|
||||||
|
securedBy: [ null, cookie, token, oauth_2_0 ]
|
||||||
get:
|
get:
|
||||||
is: [ searchable: { searchFields: "name" }, orderable, pageable ]
|
is: [ searchable: { searchFields: "name" }, orderable, pageable ]
|
||||||
responses:
|
responses:
|
||||||
|
@ -6,6 +7,15 @@ get:
|
||||||
body:
|
body:
|
||||||
application/json:
|
application/json:
|
||||||
example: !include ../examples/users.json
|
example: !include ../examples/users.json
|
||||||
|
/{id}:
|
||||||
|
#type: item
|
||||||
|
securedBy: [ null, cookie, token, oauth_2_0 ]
|
||||||
|
get:
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
body:
|
||||||
|
application/json:
|
||||||
|
example: !include ../examples/user.json
|
||||||
/current:
|
/current:
|
||||||
#type: item
|
#type: item
|
||||||
get:
|
get:
|
||||||
|
@ -14,11 +24,3 @@ get:
|
||||||
body:
|
body:
|
||||||
application/json:
|
application/json:
|
||||||
example: !include ../examples/current_user.json
|
example: !include ../examples/current_user.json
|
||||||
/{id}:
|
|
||||||
#type: item
|
|
||||||
get:
|
|
||||||
responses:
|
|
||||||
200:
|
|
||||||
body:
|
|
||||||
application/json:
|
|
||||||
example: !include ../examples/user.json
|
|
||||||
|
|
3
doc/api/pages/cookie_tutorial.md
Normal file
3
doc/api/pages/cookie_tutorial.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
One way to access the API is through your browser. Log into metamaps.cc normally, then browse manually to https://metamaps.cc/api/v2/user/current. You should see a JSON description of your own user object in the database. You can browse any GET endpoint by simply going to that URL and appending query parameters in the URI.
|
||||||
|
|
||||||
|
To run a POST or DELETE request, you can use the Fetch API. See the example in the next section.
|
2
doc/api/pages/getting-started.md
Normal file
2
doc/api/pages/getting-started.md
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
There are three ways to log in: cookie-based authentication, token-based authentication, or OAuth 2. If you're testing the API or making simple scripts, cookie-based or token-based is the best. If you're developing and app and want users to be able to log into Metamaps inside your app, you'll be able to use the OAuth 2 mechanism. Check the security tab of any of the endpoints above for instructions on logging in.
|
||||||
|
|
41
doc/api/pages/oauth_2_0_tutorial.md
Normal file
41
doc/api/pages/oauth_2_0_tutorial.md
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
We use a flow for Oauth 2 authentication called Authorization Code. It basically consists of an exchange of an `authorization` token for an `access token`. For more detailed info, check out the [RFC spec here](http://tools.ietf.org/html/rfc6749#section-4.1)
|
||||||
|
|
||||||
|
The first step is to register your client app.
|
||||||
|
|
||||||
|
#### Registering the client
|
||||||
|
|
||||||
|
Set up a new client in `/oauth/applications/new`. For testing purposes, you should fill in the redirect URI field with `urn:ietf:wg:oauth:2.0:oob`. This will tell it to display the authorization code instead of redirecting to a client application (that you don't have now).
|
||||||
|
|
||||||
|
#### Requesting authorization
|
||||||
|
|
||||||
|
To request the authorization token, you should visit the `/oauth/authorize` endpoint. You can do that either by clicking in the link to the authorization page in the app details or by visiting manually the URL:
|
||||||
|
|
||||||
|
```
|
||||||
|
http://metamaps.cc/oauth/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=urn:ietf:wg:oauth:2.0:oob&response_type=code
|
||||||
|
```
|
||||||
|
|
||||||
|
Once you are there, you should sign in and click on `Authorize`.
|
||||||
|
You will then see a response that contains your "authorization code", which you need to exchange for an access token.
|
||||||
|
|
||||||
|
#### Requesting the access token
|
||||||
|
|
||||||
|
To request the access token, you should use the returned code and exchange it for an access token. To do that you can use any HTTP client. Here's an example with `fetch`
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
fetch('https://metamaps.cc/oauth/token?client_id=THE_ID&client_secret=THE_SECRET&code=RETURNED_CODE&grant_type=authorization_code&redirect_uri=urn:ietf:wg:oauth:2.0:oob', {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'same-origin'
|
||||||
|
}).then(response => {
|
||||||
|
return response.json()
|
||||||
|
}).then(console.log).catch(console.error)
|
||||||
|
|
||||||
|
# The response will be like
|
||||||
|
{
|
||||||
|
"access_token": "de6780bc506a0446309bd9362820ba8aed28aa506c71eedbe1c5c4f9dd350e54",
|
||||||
|
"token_type": "bearer",
|
||||||
|
"expires_in": 7200,
|
||||||
|
"refresh_token": "8257e65c97202ed1726cf9571600918f3bffb2544b26e00a61df9897668c33a1"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can now make requests to the API with the access token returned.
|
25
doc/api/pages/token_tutorial.md
Normal file
25
doc/api/pages/token_tutorial.md
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
If you are logged into the API via another means, you can create a token. Once you have this token, you can append it to a request. For example, opening a private window in your browser and browsing to `https://metamaps.cc/api/v2/user/current?token=...token here...` would show you your current user, even without logging in by another means.
|
||||||
|
|
||||||
|
To get a list of your current tokens, you can log in using cookie-based authentication and run the following fetch request in your browser console (assuming the current tab is on some page within the `metamaps.cc` website.
|
||||||
|
|
||||||
|
```
|
||||||
|
fetch('/api/v2/tokens', {
|
||||||
|
method: 'GET',
|
||||||
|
credentials: 'same-origin' // needed to use the cookie-based auth
|
||||||
|
}).then(response => {
|
||||||
|
return response.json()
|
||||||
|
}).then(console.log).catch(console.error)
|
||||||
|
```
|
||||||
|
|
||||||
|
If this is your first time accessing the API, this list wil be empty. You can create a token using a similar method:
|
||||||
|
|
||||||
|
```
|
||||||
|
fetch('/api/v2/tokens', {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'same-origin'
|
||||||
|
}).then(response => {
|
||||||
|
return response.json()
|
||||||
|
}).then(console.log).catch(console.error)
|
||||||
|
```
|
||||||
|
|
||||||
|
`payload.data.token` will contain a string which you can use to append to requests to access the API from anywhere.
|
3
doc/api/securitySchemes/cookie.raml
Normal file
3
doc/api/securitySchemes/cookie.raml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
description: !include ../pages/cookie_tutorial.md
|
||||||
|
type: x-cookie
|
||||||
|
displayName: Secured by cookie-based authentication
|
|
@ -1,5 +1,4 @@
|
||||||
description: |
|
description: !include ../pages/oauth_2_0_tutorial.md
|
||||||
OAuth 2.0 implementation
|
|
||||||
type: OAuth 2.0
|
type: OAuth 2.0
|
||||||
settings:
|
settings:
|
||||||
authorizationUri: https://metamaps.cc/api/v2/oauth/authorize
|
authorizationUri: https://metamaps.cc/api/v2/oauth/authorize
|
||||||
|
|
3
doc/api/securitySchemes/token.raml
Normal file
3
doc/api/securitySchemes/token.raml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
description: !include ../pages/token_tutorial.md
|
||||||
|
type: x-token
|
||||||
|
displayName: Secured by token-based authentication
|
61
doc/api/templates/item.nunjucks
Normal file
61
doc/api/templates/item.nunjucks
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
<li>
|
||||||
|
{% if item.displayName %}
|
||||||
|
<strong>{{ item.displayName }}</strong>:
|
||||||
|
{% else %}
|
||||||
|
<strong>{{ item.key }}</strong>:
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if not item.structuredValue %}
|
||||||
|
<em>
|
||||||
|
{%- if item.required -%}required {% endif -%}
|
||||||
|
(
|
||||||
|
{%- if item.enum -%}
|
||||||
|
{%- if item.enum.length === 1 -%}
|
||||||
|
{{ item.enum.join(', ') }}
|
||||||
|
{%- else -%}
|
||||||
|
one of {{ item.enum.join(', ') }}
|
||||||
|
{%- endif -%}
|
||||||
|
{%- else -%}
|
||||||
|
{{ item.type }}
|
||||||
|
{%- endif -%}
|
||||||
|
|
||||||
|
{%- if item.default or item.default == 0 or item.default == false %} - default: {{ item.default }}{%- endif -%}
|
||||||
|
{%- if item.repeat %} - repeat: {{ item.repeat }}{%- endif -%}
|
||||||
|
{%- if item.type == 'string' -%}
|
||||||
|
{%- if item.minLength or item.minLength == 0 %} - minLength: {{ item.minLength }}{%- endif -%}
|
||||||
|
{%- if item.maxLength or item.maxLength == 0 %} - maxLength: {{ item.maxLength }}{%- endif -%}
|
||||||
|
{%- else -%}
|
||||||
|
{%- if item.minimum or item.minimum == 0 %} - minimum: {{ item.minimum }}{%- endif -%}
|
||||||
|
{%- if item.maximum or item.maximum == 0 %} - maximum: {{ item.maximum }}{%- endif -%}
|
||||||
|
{%- endif -%}
|
||||||
|
{%- if item.pattern %} - pattern: {{ item.pattern }}{%- endif -%}
|
||||||
|
)
|
||||||
|
</em>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% markdown %}
|
||||||
|
{{ item.description }}
|
||||||
|
{% endmarkdown %}
|
||||||
|
|
||||||
|
{#
|
||||||
|
{% if item.type %}
|
||||||
|
<p><strong>Type</strong>:</p>
|
||||||
|
<pre><code>{{ item.type | escape }}</code></pre>
|
||||||
|
{% endif %}
|
||||||
|
#}
|
||||||
|
|
||||||
|
{% if item.examples.length %}
|
||||||
|
<p><strong>Examples</strong>:</p>
|
||||||
|
{% for example in item.examples %}
|
||||||
|
{% if item.type == 'string' %}
|
||||||
|
<pre>{{ example | escape }}</pre>
|
||||||
|
{% else %}
|
||||||
|
<pre><code>{{ example | escape }}</code></pre>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if item.structuredValue %}
|
||||||
|
<pre><code>{{ item.structuredValue | dump }}</code></pre>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
314
doc/api/templates/resource.nunjucks
Normal file
314
doc/api/templates/resource.nunjucks
Normal file
|
@ -0,0 +1,314 @@
|
||||||
|
{% if (resource.methods or (resource.description and resource.parentUrl)) %}
|
||||||
|
<div class="panel panel-white">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h4 class="panel-title">
|
||||||
|
<a class="collapsed" data-toggle="collapse" href="#panel_{{ resource.uniqueId }}">
|
||||||
|
<span class="parent">{{ resource.parentUrl }}</span>{{ resource.relativeUri }}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<span class="methods">
|
||||||
|
{% for method in resource.methods %}
|
||||||
|
<a href="#{{ resource.uniqueId }}_{{ method.method }}"><!-- modal shown by hashchange event -->
|
||||||
|
<span class="badge badge_{{ method.method }}">{{ method.method }}
|
||||||
|
{% if method.securedBy.length %}
|
||||||
|
{% if method.securedBy | first == null %}
|
||||||
|
<span class="glyphicon glyphicon-transfer" title="Authentication not required"></span>
|
||||||
|
{% endif %}
|
||||||
|
<span class="glyphicon glyphicon-lock" title="Authentication required"></span>
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
{% endfor %}
|
||||||
|
</span>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="panel_{{ resource.uniqueId }}" class="panel-collapse collapse">
|
||||||
|
<div class="panel-body">
|
||||||
|
{% if resource.parentUrl %}
|
||||||
|
{% if resource.description %}
|
||||||
|
<div class="resource-description">
|
||||||
|
{% markdown %}
|
||||||
|
{{ resource.description }}
|
||||||
|
{% endmarkdown %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="list-group">
|
||||||
|
{% for method in resource.methods %}
|
||||||
|
<div onclick="window.location.href = '#{{ resource.uniqueId }}_{{ method.method }}'" class="list-group-item">
|
||||||
|
<span class="badge badge_{{ method.method }}">
|
||||||
|
{{ method.method }}
|
||||||
|
{% if method.securedBy.length %}
|
||||||
|
{% if method.securedBy | first == null %}
|
||||||
|
<span class="glyphicon glyphicon-transfer" title="Authentication not required"></span>
|
||||||
|
{% endif %}
|
||||||
|
<span class="glyphicon glyphicon-lock" title="Authentication required"></span>
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
<div class="method_description">
|
||||||
|
{% markdown %}
|
||||||
|
{{ method.description}}
|
||||||
|
{% endmarkdown %}
|
||||||
|
</div>
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% for method in resource.methods %}
|
||||||
|
<div class="modal fade" tabindex="0" id="{{ resource.uniqueId }}_{{ method.method }}">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||||
|
<h4 class="modal-title" id="myModalLabel">
|
||||||
|
<span class="badge badge_{{ method.method }}">
|
||||||
|
{{ method.method }}
|
||||||
|
{% if method.securedBy.length %}
|
||||||
|
{% if method.securedBy | first == null %}
|
||||||
|
<span class="glyphicon glyphicon-transfer" title="Authentication not required"></span>
|
||||||
|
{% endif %}
|
||||||
|
<span class="glyphicon glyphicon-lock" title="Authentication required"></span>
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
<span class="parent">{{ resource.parentUrl }}</span>{{ resource.relativeUri }}
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
{% if method.description %}
|
||||||
|
<div class="alert alert-info">
|
||||||
|
{% markdown %}
|
||||||
|
{{ method.description}}
|
||||||
|
{% endmarkdown %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Nav tabs -->
|
||||||
|
<ul class="nav nav-tabs">
|
||||||
|
{% if method.allUriParameters.length or method.queryString or method.queryParameters or method.headers or method.body %}
|
||||||
|
<li class="active">
|
||||||
|
<a href="#{{ resource.uniqueId }}_{{ method.method }}_request" data-toggle="tab">Request</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if method.responses %}
|
||||||
|
<li{%
|
||||||
|
if not method.allUriParameters.length and not method.queryParameters
|
||||||
|
and not method.queryString
|
||||||
|
and not method.headers and not method.body
|
||||||
|
%} class="active"{%
|
||||||
|
endif
|
||||||
|
%}>
|
||||||
|
<a href="#{{ resource.uniqueId }}_{{ method.method }}_response" data-toggle="tab">Response</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if method.securedBy.length %}
|
||||||
|
<li>
|
||||||
|
<a href="#{{ resource.uniqueId }}_{{ method.method }}_securedby" data-toggle="tab">Security</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<!-- Tab panes -->
|
||||||
|
<div class="tab-content">
|
||||||
|
{% if method.allUriParameters.length or method.queryString or method.queryParameters or method.headers or method.body %}
|
||||||
|
<div class="tab-pane active" id="{{ resource.uniqueId }}_{{ method.method }}_request">
|
||||||
|
{% if resource.allUriParameters.length %}
|
||||||
|
<h3>URI Parameters</h3>
|
||||||
|
<ul>
|
||||||
|
{% for item in resource.allUriParameters %}
|
||||||
|
{% include "./item.nunjucks" %}
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if method.annotations.length %}
|
||||||
|
<h3>Annotations</h3>
|
||||||
|
<ul>
|
||||||
|
{% for item in method.annotations %}
|
||||||
|
{% include "./item.nunjucks" %}
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if method.headers.length %}
|
||||||
|
<h3>Headers</h3>
|
||||||
|
<ul>
|
||||||
|
{% for item in method.headers %}
|
||||||
|
{% include "./item.nunjucks" %}
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if method.queryString and method.queryString.properties.length %}
|
||||||
|
<h3>Query String</h3>
|
||||||
|
<ul>
|
||||||
|
{% for item in method.queryString.properties %}
|
||||||
|
{% include "./item.nunjucks" %}
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if method.queryParameters.length %}
|
||||||
|
<h3>Query Parameters</h3>
|
||||||
|
<ul>
|
||||||
|
{% for item in method.queryParameters %}
|
||||||
|
{% include "./item.nunjucks" %}
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if method.body %}
|
||||||
|
<h3>Body</h3>
|
||||||
|
{% for b in method.body %}
|
||||||
|
<p><strong>Type: {{ b.key }}</strong></p>
|
||||||
|
|
||||||
|
{#
|
||||||
|
{% if b.type %}
|
||||||
|
<p><strong>Type</strong>:</p>
|
||||||
|
<pre><code>{{ b.type | escape }}</code></pre>
|
||||||
|
{% endif %}
|
||||||
|
#}
|
||||||
|
|
||||||
|
{% if b.properties.length %}
|
||||||
|
<strong>Properties</strong>
|
||||||
|
<ul>
|
||||||
|
{% for item in b.properties %}
|
||||||
|
{% include "./item.nunjucks" %}
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if b.examples.length %}
|
||||||
|
<p><strong>Examples</strong>:</p>
|
||||||
|
{% for example in b.examples %}
|
||||||
|
<pre><code>{{ example | escape }}</code></pre>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if method.responses %}
|
||||||
|
<div class="tab-pane{%
|
||||||
|
if not method.allUriParameters.length and not method.queryParameters.length
|
||||||
|
and not method.queryString
|
||||||
|
and not method.headers.length and not method.body.length
|
||||||
|
%} active{%
|
||||||
|
endif
|
||||||
|
%}" id="{{ resource.uniqueId }}_{{ method.method }}_response">
|
||||||
|
{% for response in method.responses %}
|
||||||
|
<h2>HTTP status code <a href="http://httpstatus.es/{{ response.code }}" target="_blank">{{ response.code }}</a></h2>
|
||||||
|
{% markdown %}
|
||||||
|
{{ response.description}}
|
||||||
|
{% endmarkdown %}
|
||||||
|
|
||||||
|
{% if response.headers.length %}
|
||||||
|
<h3>Headers</h3>
|
||||||
|
<ul>
|
||||||
|
{% for item in response.headers %}
|
||||||
|
{% include "./item.nunjucks" %}
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if response.body.length %}
|
||||||
|
<h3>Body</h3>
|
||||||
|
{% for b in response.body %}
|
||||||
|
<p><strong>Type: {{ b.key }}</strong></p>
|
||||||
|
|
||||||
|
{#
|
||||||
|
{% if b.type %}
|
||||||
|
<p><strong>Type</strong>:</p>
|
||||||
|
<pre><code>{{ b.type | escape }}</code></pre>
|
||||||
|
{% endif %}
|
||||||
|
#}
|
||||||
|
|
||||||
|
{% if b.properties.length %}
|
||||||
|
<strong>Properties</strong>
|
||||||
|
<ul>
|
||||||
|
{% for item in b.properties %}
|
||||||
|
{% include "./item.nunjucks" %}
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if b.examples.length %}
|
||||||
|
<p><strong>Examples</strong>:</p>
|
||||||
|
{% for example in b.examples %}
|
||||||
|
<pre><code>{{ example | escape }}</code></pre>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if method.securedBy.length %}
|
||||||
|
<div class="tab-pane" id="{{ resource.uniqueId }}_{{ method.method }}_securedby">
|
||||||
|
{% for securedBy in method.securedBy %}
|
||||||
|
{% if securedBy == null %}
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<span class="glyphicon glyphicon-transfer" title="Authentication not required"></span>
|
||||||
|
This route can be accessed anonymously.</h1>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
{% set securityScheme = securitySchemes[securedBy] %}
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
{% set securedByScopes = renderSecuredBy(securedBy) %}
|
||||||
|
<span class="glyphicon glyphicon-lock" title="Authentication required"></span> Secured by {{ securedByScopes }}
|
||||||
|
{% set securityScheme = securitySchemes[securedBy] %}
|
||||||
|
{% if securityScheme.description %}
|
||||||
|
{% markdown %}
|
||||||
|
{{ securityScheme.description }}
|
||||||
|
{% endmarkdown %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if securityScheme.describedBy.headers.length %}
|
||||||
|
<h3>Headers</h3>
|
||||||
|
<ul>
|
||||||
|
{% for item in securityScheme.describedBy.headers %}
|
||||||
|
{% include "./item.nunjucks" %}
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% for response in securityScheme.describedBy.responses.length %}
|
||||||
|
<h2>HTTP status code <a href="http://httpstatus.es/{{ response.code }}" target="_blank">{{ response.code }}</a></h2>
|
||||||
|
{% markdown %}
|
||||||
|
{{ response.description}}
|
||||||
|
{% endmarkdown %}
|
||||||
|
{% if response.headers.length %}
|
||||||
|
<h3>Headers</h3>
|
||||||
|
<ul>
|
||||||
|
{% for item in response.headers %}
|
||||||
|
{% include "./item.nunjucks" %}
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% for resource in resource.resources %}
|
||||||
|
{% include "./resource.nunjucks" %}
|
||||||
|
{% endfor %}
|
232
doc/api/templates/template.nunjucks
Normal file
232
doc/api/templates/template.nunjucks
Normal file
|
@ -0,0 +1,232 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>{{ title }} API documentation</title>
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||||
|
<meta name="generator" content="https://github.com/raml2html/raml2html {{ config.raml2HtmlVersion }}">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="https://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.3.0/styles/default.min.css">
|
||||||
|
<script type="text/javascript" src="https://code.jquery.com/jquery-1.11.0.min.js"></script>
|
||||||
|
<script type="text/javascript" src="https://netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
|
||||||
|
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.3.0/highlight.min.js"></script>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('.page-header pre code, .top-resource-description pre code, .modal-body pre code').each(function(i, block) {
|
||||||
|
hljs.highlightBlock(block);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('[data-toggle]').click(function() {
|
||||||
|
var selector = $(this).data('target') + ' pre code';
|
||||||
|
$(selector).each(function(i, block) {
|
||||||
|
hljs.highlightBlock(block);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// open modal on hashes like #_action_get
|
||||||
|
$(window).bind('hashchange', function(e) {
|
||||||
|
var anchor_id = document.location.hash.substr(1); //strip #
|
||||||
|
var element = $('#' + anchor_id);
|
||||||
|
|
||||||
|
// do we have such element + is it a modal? --> show it
|
||||||
|
if (element.length && element.hasClass('modal')) {
|
||||||
|
element.modal('show');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// execute hashchange on first page load
|
||||||
|
$(window).trigger('hashchange');
|
||||||
|
|
||||||
|
// remove url fragment on modal hide
|
||||||
|
$('.modal').on('hidden.bs.modal', function() {
|
||||||
|
try {
|
||||||
|
if (history && history.replaceState) {
|
||||||
|
history.replaceState({}, '', '#');
|
||||||
|
}
|
||||||
|
} catch(e) {}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.hljs {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
.parent {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
.list-group-item > .badge {
|
||||||
|
float: none;
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
.panel-title > .methods {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
.badge {
|
||||||
|
border-radius: 0;
|
||||||
|
text-transform: uppercase;
|
||||||
|
width: 70px;
|
||||||
|
font-weight: normal;
|
||||||
|
color: #f3f3f6;
|
||||||
|
line-height: normal;
|
||||||
|
}
|
||||||
|
.badge_get {
|
||||||
|
background-color: #63a8e2;
|
||||||
|
}
|
||||||
|
.badge_post {
|
||||||
|
background-color: #6cbd7d;
|
||||||
|
}
|
||||||
|
.badge_put {
|
||||||
|
background-color: #22bac4;
|
||||||
|
}
|
||||||
|
.badge_delete {
|
||||||
|
background-color: #d26460;
|
||||||
|
}
|
||||||
|
.badge_patch {
|
||||||
|
background-color: #ccc444;
|
||||||
|
}
|
||||||
|
.list-group, .panel-group {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.panel-group .panel+.panel-white {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
.panel-group .panel-white {
|
||||||
|
border-bottom: 1px solid #F5F5F5;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
.panel-white:last-child {
|
||||||
|
border-bottom-color: white;
|
||||||
|
-webkit-box-shadow: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
.panel-white .panel-heading {
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
.tab-pane ul {
|
||||||
|
padding-left: 2em;
|
||||||
|
}
|
||||||
|
.tab-pane h1 {
|
||||||
|
font-size: 1.3em;
|
||||||
|
}
|
||||||
|
.tab-pane h2 {
|
||||||
|
font-size: 1.2em;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
.tab-pane h3 {
|
||||||
|
font-size: 1.1em;
|
||||||
|
}
|
||||||
|
.tab-content {
|
||||||
|
border-left: 1px solid #ddd;
|
||||||
|
border-right: 1px solid #ddd;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
#sidebar {
|
||||||
|
margin-top: 30px;
|
||||||
|
padding-right: 5px;
|
||||||
|
overflow: auto;
|
||||||
|
height: 90%;
|
||||||
|
}
|
||||||
|
.top-resource-description {
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
background: #fcfcfc;
|
||||||
|
padding: 15px 15px 0 15px;
|
||||||
|
margin: -15px -15px 10px -15px;
|
||||||
|
}
|
||||||
|
.resource-description {
|
||||||
|
border-bottom: 1px solid #fcfcfc;
|
||||||
|
background: #fcfcfc;
|
||||||
|
padding: 15px 15px 0 15px;
|
||||||
|
margin: -15px -15px 10px -15px;
|
||||||
|
}
|
||||||
|
.resource-description p:last-child {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.list-group .badge {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
.method_description {
|
||||||
|
margin-left: 85px;
|
||||||
|
}
|
||||||
|
.method_description p:last-child {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.list-group-item {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.list-group-item:hover {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre code {
|
||||||
|
overflow: auto;
|
||||||
|
word-wrap: normal;
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body data-spy="scroll" data-target="#sidebar">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-9" role="main">
|
||||||
|
<div class="page-header">
|
||||||
|
<h1>{{ title }} API documentation{% if version %} <small>version {{ version }}</small>{% endif %}</h1>
|
||||||
|
<p>{{ baseUri }}</p>
|
||||||
|
|
||||||
|
{% if baseUriParameters %}
|
||||||
|
<ul>
|
||||||
|
{% for item in baseUriParameters %}
|
||||||
|
{% include "./item.nunjucks" %}
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% for resource in resources %}
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h3 id="{{ resource.uniqueId }}" class="panel-title">{% if resource.displayName %}{{ resource.displayName}}{% else %}{{ resource.relativeUri }}{% endif %}</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel-body">
|
||||||
|
{% if resource.description %}
|
||||||
|
<div class="top-resource-description">
|
||||||
|
{% markdown %}
|
||||||
|
{{ resource.description }}
|
||||||
|
{% endmarkdown %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="panel-group">
|
||||||
|
{% include "./resource.nunjucks" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% for chapter in documentation %}
|
||||||
|
<h3 id="{{ chapter.uniqueId }}"><a href="#{{ chapter.uniqueId }}">{{ chapter.title }}</a></h3>
|
||||||
|
{% markdown %}
|
||||||
|
{{ chapter.content }}
|
||||||
|
{% endmarkdown %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div id="sidebar" class="hidden-print affix" role="complementary">
|
||||||
|
<ul class="nav nav-pills nav-stacked">
|
||||||
|
{% for resource in resources %}
|
||||||
|
<li><a href="#{{ resource.uniqueId}}">{% if resource.displayName %}{{ resource.displayName}}{% else %}{{ resource.relativeUri }}{% endif %}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -63,7 +63,7 @@ _Backbone.Map = Backbone.Model.extend({
|
||||||
this.on('saved', this.savedEvent)
|
this.on('saved', this.savedEvent)
|
||||||
},
|
},
|
||||||
savedEvent: function () {
|
savedEvent: function () {
|
||||||
Realtime.sendMapChange(this)
|
Realtime.updateMap(this)
|
||||||
},
|
},
|
||||||
authorizeToEdit: function (mapper) {
|
authorizeToEdit: function (mapper) {
|
||||||
if (mapper && (
|
if (mapper && (
|
||||||
|
@ -370,7 +370,7 @@ _Backbone.init = function () {
|
||||||
return node
|
return node
|
||||||
},
|
},
|
||||||
savedEvent: function () {
|
savedEvent: function () {
|
||||||
Realtime.sendTopicChange(this)
|
Realtime.updateTopic(this)
|
||||||
},
|
},
|
||||||
updateViews: function () {
|
updateViews: function () {
|
||||||
var onPageWithTopicCard = Active.Map || Active.Topic
|
var onPageWithTopicCard = Active.Map || Active.Topic
|
||||||
|
@ -549,7 +549,7 @@ _Backbone.init = function () {
|
||||||
return edge
|
return edge
|
||||||
},
|
},
|
||||||
savedEvent: function () {
|
savedEvent: function () {
|
||||||
Realtime.sendSynapseChange(this)
|
Realtime.updateSynapse(this)
|
||||||
},
|
},
|
||||||
updateViews: function () {
|
updateViews: function () {
|
||||||
this.updateCardView()
|
this.updateCardView()
|
||||||
|
|
38
frontend/src/Metamaps/GlobalUI/ImportDialog.js
Normal file
38
frontend/src/Metamaps/GlobalUI/ImportDialog.js
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
/* global $ */
|
||||||
|
|
||||||
|
import React from 'react'
|
||||||
|
import ReactDOM from 'react-dom'
|
||||||
|
import outdent from 'outdent'
|
||||||
|
|
||||||
|
import ImportDialogBox from '../../components/ImportDialogBox'
|
||||||
|
|
||||||
|
import PasteInput from '../PasteInput'
|
||||||
|
|
||||||
|
const ImportDialog = {
|
||||||
|
openLightbox: null,
|
||||||
|
closeLightbox: null,
|
||||||
|
|
||||||
|
init: function (serverData, openLightbox, closeLightbox) {
|
||||||
|
const self = ImportDialog
|
||||||
|
self.openLightbox = openLightbox
|
||||||
|
self.closeLightbox = closeLightbox
|
||||||
|
|
||||||
|
$('#lightbox_content').append($(outdent`
|
||||||
|
<div class="lightboxContent" id="import-dialog-lightbox">
|
||||||
|
<div class="importDialogWrapper" />
|
||||||
|
</div>
|
||||||
|
`))
|
||||||
|
ReactDOM.render(React.createElement(ImportDialogBox, {
|
||||||
|
onFileAdded: PasteInput.handleFile,
|
||||||
|
exampleImageUrl: serverData['import-example.png']
|
||||||
|
}), $('.importDialogWrapper').get(0))
|
||||||
|
},
|
||||||
|
show: function () {
|
||||||
|
ImportDialog.openLightbox('import-dialog')
|
||||||
|
},
|
||||||
|
hide: function () {
|
||||||
|
ImportDialog.closeLightbox('import-dialog')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ImportDialog
|
|
@ -1,11 +1,14 @@
|
||||||
/* global Metamaps, $ */
|
/* global Metamaps, $ */
|
||||||
|
|
||||||
|
import clipboard from 'clipboard-js'
|
||||||
|
|
||||||
import Active from '../Active'
|
import Active from '../Active'
|
||||||
import Create from '../Create'
|
import Create from '../Create'
|
||||||
|
|
||||||
import Search from './Search'
|
import Search from './Search'
|
||||||
import CreateMap from './CreateMap'
|
import CreateMap from './CreateMap'
|
||||||
import Account from './Account'
|
import Account from './Account'
|
||||||
|
import ImportDialog from './ImportDialog'
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Metamaps.Backbone
|
* Metamaps.Backbone
|
||||||
|
@ -21,6 +24,7 @@ const GlobalUI = {
|
||||||
self.Search.init()
|
self.Search.init()
|
||||||
self.CreateMap.init()
|
self.CreateMap.init()
|
||||||
self.Account.init()
|
self.Account.init()
|
||||||
|
self.ImportDialog.init(Metamaps.Erb, self.openLightbox, self.closeLightbox)
|
||||||
|
|
||||||
if ($('#toast').html().trim()) self.notifyUser($('#toast').html())
|
if ($('#toast').html().trim()) self.notifyUser($('#toast').html())
|
||||||
|
|
||||||
|
@ -137,9 +141,19 @@ const GlobalUI = {
|
||||||
self.hideDiv('#toast')
|
self.hideDiv('#toast')
|
||||||
},
|
},
|
||||||
shareInvite: function (inviteLink) {
|
shareInvite: function (inviteLink) {
|
||||||
window.prompt('To copy the invite link, press: Ctrl+C, Enter', inviteLink)
|
clipboard.copy({
|
||||||
|
'text/plain': inviteLink
|
||||||
|
}).then(() => {
|
||||||
|
$('#joinCodesBox .popup').remove()
|
||||||
|
$('#joinCodesBox').append('<p class="popup" style="text-align: center">Copied!</p>')
|
||||||
|
window.setTimeout(() => $('#joinCodesBox .popup').remove(), 1500)
|
||||||
|
}, () => {
|
||||||
|
$('#joinCodesBox .popup').remove()
|
||||||
|
$('#joinCodesBox').append(`<p class="popup" style="text-align: center">Your browser doesn't support copying, please copy manually.</p>`)
|
||||||
|
window.setTimeout(() => $('#joinCodesBox .popup').remove(), 1500)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Search, CreateMap, Account }
|
export { Search, CreateMap, Account, ImportDialog }
|
||||||
export default GlobalUI
|
export default GlobalUI
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue