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 = {
|
||||
"sourceType": "module",
|
||||
"parser": "babel-eslint",
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
}
|
||||
},
|
||||
"extends": "standard",
|
||||
"installedESLint": true,
|
||||
"env": {
|
||||
|
@ -13,6 +18,8 @@ module.exports = {
|
|||
"react"
|
||||
],
|
||||
"rules": {
|
||||
"react/jsx-uses-react": [2],
|
||||
"react/jsx-uses-vars": [2],
|
||||
"yoda": [2, "never", { "exceptRange": true }]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ export SECRET_KEY_BASE='267c8a84f63963282f45bc3010eaddf027abfab58fc759d6e239c800
|
|||
# export S3_BUCKET_NAME
|
||||
# export AWS_ACCESS_KEY_ID
|
||||
# export AWS_SECRET_ACCESS_KEY
|
||||
# export SSO_KEY
|
||||
#
|
||||
# export SMTP_DOMAIN
|
||||
# export SMTP_PASSWORD
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
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
|
||||
|
|
2
Gemfile
2
Gemfile
|
@ -25,10 +25,8 @@ gem 'rack-cors'
|
|||
gem 'redis'
|
||||
gem 'slack-notifier'
|
||||
gem 'snorlax'
|
||||
gem 'uservoice-ruby'
|
||||
|
||||
# asset stuff
|
||||
gem 'coffee-rails'
|
||||
gem 'jquery-rails'
|
||||
gem 'jquery-ui-rails'
|
||||
gem 'sass-rails'
|
||||
|
|
17
Gemfile.lock
17
Gemfile.lock
|
@ -70,13 +70,6 @@ GEM
|
|||
cocaine (0.5.8)
|
||||
climate_control (>= 0.0.3, < 1.0)
|
||||
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)
|
||||
debug_inspector (0.0.2)
|
||||
delayed_job (4.1.2)
|
||||
|
@ -103,7 +96,6 @@ GEM
|
|||
actionmailer (>= 4.0, < 6)
|
||||
activesupport (>= 4.0, < 6)
|
||||
execjs (2.7.0)
|
||||
ezcrypto (0.7.2)
|
||||
factory_girl (4.7.0)
|
||||
activesupport (>= 3.0.0)
|
||||
factory_girl_rails (4.7.0)
|
||||
|
@ -145,7 +137,6 @@ GEM
|
|||
nokogiri (1.6.8)
|
||||
mini_portile2 (~> 2.1.0)
|
||||
pkg-config (~> 1.1.7)
|
||||
oauth (0.5.1)
|
||||
orm_adapter (0.5.0)
|
||||
paperclip (5.1.0)
|
||||
activemodel (>= 4.2.0)
|
||||
|
@ -262,10 +253,6 @@ GEM
|
|||
uglifier (3.0.2)
|
||||
execjs (>= 0.3.0, < 3)
|
||||
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)
|
||||
rack (>= 1.0)
|
||||
websocket-driver (0.6.4)
|
||||
|
@ -282,7 +269,6 @@ DEPENDENCIES
|
|||
better_errors
|
||||
binding_of_caller
|
||||
brakeman
|
||||
coffee-rails
|
||||
delayed_job
|
||||
delayed_job_active_record
|
||||
devise
|
||||
|
@ -315,10 +301,9 @@ DEPENDENCIES
|
|||
snorlax
|
||||
tunemygc
|
||||
uglifier
|
||||
uservoice-ruby
|
||||
|
||||
RUBY VERSION
|
||||
ruby 2.3.0p0
|
||||
|
||||
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:
|
||||
|
||||
## Community
|
||||
|
||||
- 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.
|
||||
## How do I learn more?
|
||||
|
||||
- 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 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]
|
||||
|
||||
## 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
|
||||
|
||||
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
|
||||
|
||||
[site-blog]: http://blog.metamaps.cc
|
||||
[site-beta]: http://metamaps.cc
|
||||
[license]: https://github.com/metamaps/metamaps/blob/develop/LICENSE
|
||||
[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 ./src/Metamaps.Erb
|
||||
//= 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_link_signifier.png'] = '<%= asset_path('topic_link_signifier.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.ogg'] = '<%= asset_path 'sounds/MM_sounds.ogg' %>'
|
||||
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') %>);
|
||||
}
|
||||
/* map info box */
|
||||
/* map info box */
|
||||
|
||||
.wrapper div.mapInfoBox {
|
||||
.wrapper .mapInfoBox {
|
||||
display: none;
|
||||
position: absolute;
|
||||
bottom: 40px;
|
||||
|
@ -1536,12 +1535,40 @@ h3.filterBox {
|
|||
background-color: #424242;
|
||||
color: #F5F5F5;
|
||||
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;
|
||||
min-height: 300px;
|
||||
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 {
|
||||
display: none;
|
||||
|
@ -2028,17 +2055,17 @@ and it won't be important on password protected instances */
|
|||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
position: absolute;
|
||||
z-index: 1000000;
|
||||
display: none;
|
||||
}
|
||||
#lightbox_main {
|
||||
width: 800px;
|
||||
height: auto;
|
||||
margin: 0 auto;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
top: 50%;
|
||||
top: 5vh;
|
||||
height: 90vh;
|
||||
background-color: transparent;
|
||||
color: black;
|
||||
}
|
||||
|
@ -2077,8 +2104,11 @@ and it won't be important on password protected instances */
|
|||
background-position: center center;
|
||||
}
|
||||
#lightbox_content {
|
||||
width: 552px;
|
||||
height: 434px;
|
||||
width: 800px;
|
||||
height: 500px;
|
||||
max-height: 90vh;
|
||||
box-sizing: border-box;
|
||||
overflow-y: auto;
|
||||
background-color: #e0e0e0;
|
||||
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);
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
}
|
||||
|
||||
|
||||
#center-container {
|
||||
position:relative;
|
||||
height:100%;
|
||||
|
@ -143,6 +142,16 @@
|
|||
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 {
|
||||
display:block;
|
||||
margin-top:2px;
|
||||
|
@ -582,10 +591,10 @@ background-color: #E0E0E0;
|
|||
position: relative;
|
||||
}
|
||||
|
||||
.CardOnGraph .hoverForTip:hover .tip, .mapCard .hoverForTip:hover .tip, #mapContribs:hover .tip {
|
||||
.CardOnGraph .hoverForTip:hover .tip, #mapContribs:hover .tip {
|
||||
display:block;
|
||||
}
|
||||
.CardOnGraph .tip, .mapCard .tip {
|
||||
.CardOnGraph .tip {
|
||||
display:none;
|
||||
position: absolute;
|
||||
background: black;
|
||||
|
@ -942,154 +951,7 @@ font-family: 'din-regular', helvetica, sans-serif;
|
|||
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 */
|
||||
|
|
|
@ -188,7 +188,7 @@
|
|||
.upperRightIcon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background-image: url(<%= asset_data_uri('topright_sprite.png') %>);
|
||||
background-image: url(<%= asset_path('topright_sprite.png') %>);
|
||||
background-repeat: no-repeat;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
@ -325,7 +325,7 @@
|
|||
}
|
||||
|
||||
.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 {
|
||||
padding: 64px 0 280px;
|
||||
|
@ -364,7 +364,7 @@
|
|||
cursor: pointer;
|
||||
}
|
||||
.openCheatsheet {
|
||||
background-image: url(<%= asset_data_uri('help_sprite.png') %>);
|
||||
background-image: url(<%= asset_path('help_sprite.png') %>);
|
||||
background-repeat:no-repeat;
|
||||
}
|
||||
.openCheatsheet:hover {
|
||||
|
@ -373,7 +373,7 @@
|
|||
.mapInfoIcon {
|
||||
position: relative;
|
||||
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;
|
||||
}
|
||||
.mapInfoIcon:hover {
|
||||
|
@ -382,8 +382,14 @@
|
|||
.mapPage .mapInfoIcon {
|
||||
top: 0;
|
||||
}
|
||||
.importDialog {
|
||||
background-image: url(<%= asset_path('import.png') %>);
|
||||
background-position: 0 0;
|
||||
background-repeat: no-repeat;
|
||||
width: 32px;
|
||||
}
|
||||
.starMap {
|
||||
background-image: url(<%= asset_data_uri('starmap_sprite.png') %>);
|
||||
background-image: url(<%= asset_path('starmap_sprite.png') %>);
|
||||
background-position: 0 0;
|
||||
background-repeat: no-repeat;
|
||||
width: 32px;
|
||||
|
@ -437,7 +443,7 @@
|
|||
.takeScreenshot {
|
||||
margin-bottom: 5px;
|
||||
border-radius: 2px;
|
||||
background-image: url(<%= asset_data_uri 'screenshot_sprite.png' %>);
|
||||
background-image: url(<%= asset_path 'screenshot_sprite.png' %>);
|
||||
display: none;
|
||||
}
|
||||
.takeScreenshot:hover {
|
||||
|
@ -450,7 +456,7 @@
|
|||
.zoomExtents {
|
||||
margin-bottom:5px;
|
||||
border-radius: 2px;
|
||||
background-image: url(<%= asset_data_uri('extents_sprite.png') %>);
|
||||
background-image: url(<%= asset_path('extents_sprite.png') %>);
|
||||
}
|
||||
|
||||
.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,
|
||||
.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;
|
||||
}
|
||||
|
||||
|
@ -623,7 +629,7 @@
|
|||
}
|
||||
|
||||
.zoomIn {
|
||||
background-image: url(<%= asset_data_uri('zoom_sprite.png') %>);
|
||||
background-image: url(<%= asset_path('zoom_sprite.png') %>);
|
||||
background-position: 0 /…0;
|
||||
border-top-left-radius: 2px;
|
||||
border-top-right-radius: 2px;
|
||||
|
@ -632,7 +638,7 @@
|
|||
background-position: -32px 0;
|
||||
}
|
||||
.zoomOut {
|
||||
background-image: url(<%= asset_data_uri('zoom_sprite.png') %>);
|
||||
background-image: url(<%= asset_path('zoom_sprite.png') %>);
|
||||
background-position:0 -32px;
|
||||
border-bottom-left-radius: 2px;
|
||||
border-bottom-right-radius: 2px;
|
||||
|
@ -650,15 +656,14 @@
|
|||
}
|
||||
|
||||
#exploreMaps {
|
||||
padding: 0 5%;
|
||||
position: absolute;
|
||||
width: 90%;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#exploreMaps > div {
|
||||
margin-top: 110px;
|
||||
margin: 110px auto 0 auto;
|
||||
}
|
||||
|
||||
.button.loadMore {
|
||||
|
@ -740,23 +745,23 @@
|
|||
left:5px;
|
||||
}
|
||||
.exploreMapsCenter .myMaps .exploreMapsIcon {
|
||||
background-image: url(<%= asset_data_uri 'exploremaps_sprite.png' %>);
|
||||
background-image: url(<%= asset_path 'exploremaps_sprite.png' %>);
|
||||
background-position: -32px 0;
|
||||
}
|
||||
.exploreMapsCenter .sharedMaps .exploreMapsIcon {
|
||||
background-image: url(<%= asset_data_uri 'exploremaps_sprite.png' %>);
|
||||
background-image: url(<%= asset_path 'exploremaps_sprite.png' %>);
|
||||
background-position: -128px 0;
|
||||
}
|
||||
.exploreMapsCenter .activeMaps .exploreMapsIcon {
|
||||
background-image: url(<%= asset_data_uri 'exploremaps_sprite.png' %>);
|
||||
background-image: url(<%= asset_path 'exploremaps_sprite.png' %>);
|
||||
background-position: 0 0;
|
||||
}
|
||||
.exploreMapsCenter .featuredMaps .exploreMapsIcon {
|
||||
background-image: url(<%= asset_data_uri 'exploremaps_sprite.png' %>);
|
||||
background-image: url(<%= asset_path 'exploremaps_sprite.png' %>);
|
||||
background-position: -96px 0;
|
||||
}
|
||||
.exploreMapsCenter .starredMaps .exploreMapsIcon {
|
||||
background-image: url(<%= asset_data_uri 'exploremaps_sprite.png' %>);
|
||||
background-image: url(<%= asset_path 'exploremaps_sprite.png' %>);
|
||||
background-position: -96px 0;
|
||||
}
|
||||
.myMaps:hover .exploreMapsIcon, .myMaps.active .exploreMapsIcon {
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
/* =USERVOICE ICON DEFINE
|
||||
--------------------------------------------------------*/
|
||||
|
||||
.unauthenticated .uv-icon {
|
||||
.unauthenticated .feedback-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.uv-icon.uv-bottom-left {
|
||||
.feedback-icon {
|
||||
position: fixed;
|
||||
background-image: url(<%= asset_data_uri 'feedback_sprite.png' %>);
|
||||
background-repeat: no-repeat;
|
||||
color:#FFFFFF;
|
||||
|
@ -20,6 +18,8 @@ div.uv-icon.uv-bottom-left {
|
|||
opacity: 1;
|
||||
}
|
||||
|
||||
div.uv-icon.uv-bottom-left:hover {
|
||||
.feedback-icon:hover {
|
||||
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%;
|
||||
}
|
||||
|
||||
.wrapper div.mapInfoBox {
|
||||
.wrapper .mapInfoBox {
|
||||
position: fixed;
|
||||
top: 50px;
|
||||
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 V1
|
||||
class DeprecatedController < ApplicationController
|
||||
# rubocop:disable Style/MethodMissing
|
||||
def method_missing
|
||||
render json: { error: '/api/v1 is deprecated! Please use /api/v2 instead.' }
|
||||
def deprecated
|
||||
render json: {
|
||||
error: '/api/v1 has been deprecated! Please use /api/v2 instead.'
|
||||
}, status: :gone
|
||||
end
|
||||
# rubocop:enable Style/MethodMissing
|
||||
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
|
||||
end
|
||||
|
||||
def catch_404
|
||||
skip_authorization
|
||||
render json: { error: '404 Not found' }, status: :not_found
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def accessible_records
|
||||
|
|
|
@ -18,12 +18,6 @@ module Api
|
|||
create_action
|
||||
respond_with_resource
|
||||
end
|
||||
|
||||
def my_tokens
|
||||
authorize resource_class
|
||||
instantiate_collection
|
||||
respond_with_collection
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -21,23 +21,14 @@ class ApplicationController < ActionController::Base
|
|||
helper_method :authenticated?
|
||||
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
|
||||
if authenticated?
|
||||
head :forbidden # TODO: make this better
|
||||
if authenticated? and params[:controller] == 'maps' and params[:action] == 'show'
|
||||
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
|
||||
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
|
||||
|
||||
|
@ -55,7 +46,7 @@ class ApplicationController < ActionController::Base
|
|||
|
||||
def require_user
|
||||
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
|
||||
end
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ class ExploreController < ApplicationController
|
|||
|
||||
# GET /explore/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|
|
||||
format.html do
|
||||
|
|
|
@ -8,7 +8,7 @@ class MainController < ApplicationController
|
|||
respond_to do |format|
|
||||
format.html do
|
||||
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)
|
||||
render 'explore/active'
|
||||
else
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
class MapsController < ApplicationController
|
||||
before_action :require_user, only: [:create, :update, :destroy, :access, :events]
|
||||
before_action :set_map, only: [:show, :update, :destroy, :access, :contains,
|
||||
:events, :export]
|
||||
before_action :require_user, only: [:create, :update, :destroy, :events]
|
||||
before_action :set_map, only: [:show, :update, :destroy, :contains, :events, :export]
|
||||
after_action :verify_authorized
|
||||
|
||||
# GET maps/:id
|
||||
|
@ -16,6 +15,7 @@ class MapsController < ApplicationController
|
|||
@allmappings = policy_scope(@map.mappings)
|
||||
@allmessages = @map.messages.sort_by(&:created_at)
|
||||
@allstars = @map.stars
|
||||
@allrequests = @map.access_requests
|
||||
end
|
||||
format.json { render json: @map }
|
||||
format.csv { redirect_to action: :export, format: :csv }
|
||||
|
@ -80,24 +80,6 @@ class MapsController < ApplicationController
|
|||
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
|
||||
def contains
|
||||
respond_to do |format|
|
||||
|
|
|
@ -22,7 +22,7 @@ class TopicsController < ApplicationController
|
|||
end
|
||||
@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
|
||||
|
||||
# GET topics/:id
|
||||
|
|
|
@ -7,6 +7,6 @@ class Users::PasswordsController < Devise::PasswordsController
|
|||
end
|
||||
|
||||
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
|
||||
|
|
|
@ -2,19 +2,34 @@
|
|||
class Users::RegistrationsController < Devise::RegistrationsController
|
||||
before_action :configure_sign_up_params, only: [:create]
|
||||
before_action :configure_account_update_params, only: [:update]
|
||||
after_action :store_location, only: [:new]
|
||||
|
||||
protected
|
||||
|
||||
def after_sign_up_path_for(resource)
|
||||
signed_in_root_path(resource)
|
||||
end
|
||||
|
||||
def after_update_path_for(resource)
|
||||
signed_in_root_path(resource)
|
||||
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
|
||||
|
||||
def store_location
|
||||
if params[:redirect_to]
|
||||
store_location_for(User, params[:redirect_to])
|
||||
end
|
||||
end
|
||||
|
||||
def configure_sign_up_params
|
||||
devise_parameter_sanitizer.permit(:sign_up, keys: [:name, :joinedwithcode])
|
||||
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
|
||||
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)
|
||||
@inviter = inviter
|
||||
@map = map
|
||||
subject = @map.name + ' - Invitation to edit'
|
||||
subject = @map.name + ' - invitation to edit'
|
||||
mail(to: invitee.email, subject: subject)
|
||||
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 :stars
|
||||
|
||||
has_many :access_requests, dependent: :destroy
|
||||
has_many :user_maps, dependent: :destroy
|
||||
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
|
||||
has_attached_file :screenshot,
|
||||
styles: {
|
||||
thumb: ['188x126#', :png]
|
||||
thumb: ['220x220#', :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 :arranged, inclusion: { in: [true, false] }
|
||||
|
@ -58,13 +59,17 @@ class Map < ApplicationRecord
|
|||
delegate :name, to: :user, prefix: true
|
||||
|
||||
def user_image
|
||||
user.image.url
|
||||
user.image.url(:thirtytwo)
|
||||
end
|
||||
|
||||
def contributor_count
|
||||
contributors.length
|
||||
end
|
||||
|
||||
def star_count
|
||||
stars.length
|
||||
end
|
||||
|
||||
def collaborator_ids
|
||||
collaborators.map(&:id)
|
||||
end
|
||||
|
@ -86,7 +91,7 @@ class Map < ApplicationRecord
|
|||
end
|
||||
|
||||
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[:updated_at_clean] = updated_at_str
|
||||
json
|
||||
|
@ -102,7 +107,8 @@ class Map < ApplicationRecord
|
|||
mappers: contributors,
|
||||
collaborators: editors,
|
||||
messages: messages.sort_by(&:created_at),
|
||||
stars: stars
|
||||
stars: stars,
|
||||
requests: access_requests
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -122,6 +128,7 @@ class Map < ApplicationRecord
|
|||
removed = current_collaborators.map(&:id).map do |old_user_id|
|
||||
next nil if user_ids.include?(old_user_id)
|
||||
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
|
||||
end
|
||||
removed.compact
|
||||
|
|
|
@ -65,6 +65,11 @@ class User < ApplicationRecord
|
|||
json
|
||||
end
|
||||
|
||||
def all_accessible_maps
|
||||
#TODO: is there a way to keep this an ActiveRecord relation?
|
||||
maps + shared_maps
|
||||
end
|
||||
|
||||
def recentMetacodes
|
||||
array = []
|
||||
self.topics.sort{|a,b| b.created_at <=> a.created_at }.each do |t|
|
||||
|
|
|
@ -37,10 +37,36 @@ class MapPolicy < ApplicationPolicy
|
|||
end
|
||||
|
||||
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
|
||||
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?
|
||||
show?
|
||||
end
|
||||
|
|
|
@ -8,11 +8,13 @@ class MappingPolicy < ApplicationPolicy
|
|||
# a private topic, since you can't see the private topic anyways
|
||||
visible = %w(public commons)
|
||||
permission = 'maps.permission IN (?)'
|
||||
if user
|
||||
scope.joins(:map).where(permission, visible).or(scope.joins(:map).where(user_id: user.id))
|
||||
else
|
||||
scope.joins(:map).where(permission, visible)
|
||||
end
|
||||
return scope.joins(:map).where(permission, visible) unless user
|
||||
|
||||
# if this is getting changed, the policy_scope for messages should also be changed
|
||||
# as it is based entirely on the map to which it belongs
|
||||
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
|
||||
|
||||
|
|
|
@ -4,11 +4,13 @@ class MessagePolicy < ApplicationPolicy
|
|||
def resolve
|
||||
visible = %w(public commons)
|
||||
permission = 'maps.permission IN (?)'
|
||||
if user
|
||||
scope.joins(:maps).where(permission + ' OR maps.user_id = ?', visible, user.id)
|
||||
else
|
||||
scope.where(permission, visible)
|
||||
end
|
||||
return scope.joins(:map).where(permission, visible) unless user
|
||||
|
||||
# if this is getting changed, the policy_scope for mappings should also be changed
|
||||
# as it is based entirely on the map to which it belongs
|
||||
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
|
||||
|
||||
|
|
|
@ -3,11 +3,10 @@ class SynapsePolicy < ApplicationPolicy
|
|||
class Scope < Scope
|
||||
def resolve
|
||||
visible = %w(public commons)
|
||||
|
||||
return scope.where(permission: visible) unless user
|
||||
|
||||
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))
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,11 +10,11 @@ class TokenPolicy < ApplicationPolicy
|
|||
end
|
||||
end
|
||||
|
||||
def create?
|
||||
def index?
|
||||
user.present?
|
||||
end
|
||||
|
||||
def my_tokens?
|
||||
def create?
|
||||
user.present?
|
||||
end
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ class TopicPolicy < ApplicationPolicy
|
|||
return scope.where(permission: visible) unless user
|
||||
|
||||
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))
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,12 +2,14 @@
|
|||
<%= render :partial => 'layouts/templates' %>
|
||||
<%= render :partial => 'shared/metacodeBgColors' %>
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
// TODO move this into Metamaps.ServerData somehow
|
||||
<% if current_user %>
|
||||
Metamaps.Active.Mapper = <%= current_user.to_json.html_safe %>
|
||||
<% else %>
|
||||
Metamaps.Active.Mapper = null;
|
||||
<% end %>
|
||||
|
||||
// TODO move this into frontend/
|
||||
Metamaps.Loading = {
|
||||
loader: new CanvasLoader('loading'),
|
||||
hide: function () {
|
||||
|
@ -22,8 +24,6 @@
|
|||
Metamaps.Loading.loader.setDensity(41); // default is 40
|
||||
Metamaps.Loading.loader.setRange(0.9); // default is 1.3
|
||||
Metamaps.Loading.loader.show(); // Hidden by default
|
||||
|
||||
USERVOICE.load();
|
||||
</script>
|
||||
<%= render :partial => 'layouts/googleanalytics' if Rails.env.production? %>
|
||||
</body>
|
||||
|
|
|
@ -74,7 +74,7 @@
|
|||
<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="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>
|
||||
|
||||
|
|
|
@ -8,11 +8,11 @@
|
|||
<div class="infoAndHelp">
|
||||
<%= render :partial => 'maps/mapinfobox' %>
|
||||
|
||||
<% starred = current_user && @map && current_user.starred_map?(@map)
|
||||
starClass = starred ? 'starred' : ''
|
||||
tooltip = starred ? 'Star' : 'Unstar' %>
|
||||
<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="openCheatsheet openLightbox infoElement mapElement" data-open="cheatsheet"><div class="tooltipsAbove">Help</div></div>
|
||||
<div class="clearfloat"></div>
|
||||
<% starred = current_user && @map && current_user.starred_map?(@map)
|
||||
starClass = starred ? 'starred' : ''
|
||||
tooltip = starred ? 'Star' : 'Unstar' %>
|
||||
<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="openCheatsheet openLightbox infoElement mapElement" data-open="cheatsheet"><div class="tooltipsAbove">Help</div></div>
|
||||
<div class="clearfloat"></div>
|
||||
</div>
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
</li>
|
||||
<% end %>
|
||||
<li>
|
||||
<%= link_to "Global Maps", explore_active_path, :data => { :router => 'true'} %>
|
||||
<%= link_to "All Maps", explore_active_path, :data => { :router => 'true'} %>
|
||||
</li>
|
||||
<% if not current_user %>
|
||||
<li>
|
||||
|
@ -42,7 +42,7 @@
|
|||
<%= link_to "Request Invite", request_path %>
|
||||
</li>
|
||||
<li>
|
||||
<%= link_to "Login", new_user_session_path %>
|
||||
<%= link_to "Login", sign_in_path %>
|
||||
</li>
|
||||
<% end %>
|
||||
<% if current_user %>
|
||||
|
|
|
@ -183,11 +183,12 @@
|
|||
<span class="title">
|
||||
<div class="titleWrapper" id="titleActivator">
|
||||
<span class="best_in_place best_in_place_name"
|
||||
data-url="/topics/{{id}}"
|
||||
data-object="topic"
|
||||
data-attribute="name"
|
||||
data-activator="#titleActivator"
|
||||
data-type="textarea">{{name}}</span>
|
||||
data-bip-url="/topics/{{id}}"
|
||||
data-bip-object="topic"
|
||||
data-bip-attribute="name"
|
||||
data-bip-activator="#titleActivator"
|
||||
data-bip-value="{{name}}"
|
||||
data-bip-type="textarea">{{name}}</span>
|
||||
</div>
|
||||
</span>
|
||||
<div class="links">
|
||||
|
@ -220,7 +221,7 @@
|
|||
</div>
|
||||
<div class="scroll">
|
||||
<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>
|
||||
</div>
|
||||
|
|
|
@ -14,11 +14,32 @@
|
|||
<div class="sidebarSearchIcon"></div>
|
||||
<div class="clearfloat"></div>
|
||||
</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><!-- end upperLeftUI -->
|
||||
|
||||
<div class="upperRightUI">
|
||||
<div class="mapElement upperRightEl upperRightMapButtons">
|
||||
<div class="importDialog infoElement mapElement openLightbox" data-open="import-dialog-lightbox">
|
||||
<div class="tooltipsAbove">Import data</div>
|
||||
</div>
|
||||
|
||||
<!-- filtering -->
|
||||
<div class="sidebarFilter upperRightEl">
|
||||
<div class="sidebarFilterIcon upperRightIcon"><div class="tooltipsUnder">Filter</div></div>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
<body class="<%= authenticated? ? "authenticated" : "unauthenticated" %>">
|
||||
|
||||
|
||||
<a class='feedback-icon' target='_blank' href='https://hylo.com/c/metamaps'></a>
|
||||
|
||||
<%= 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;">
|
||||
<% 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>
|
||||
<% if @map.desc %>
|
||||
<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) %>]
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
<% if @map %>
|
||||
<div class="mapInfoName" id="mapInfoName">
|
||||
<% 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 %>
|
||||
<%= @map.name %>
|
||||
<% end %>
|
||||
|
@ -67,7 +67,7 @@
|
|||
|
||||
<div class="mapInfoDesc" id="mapInfoDesc">
|
||||
<% 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 %>
|
||||
<%= @map.desc %>
|
||||
<% 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">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"><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 id="csEditingTopics">
|
||||
|
@ -71,7 +71,7 @@
|
|||
<span class="csTitle">Open 'Context Menu':</span> Right-click/alt+click on topic icon or synapse
|
||||
</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>
|
||||
|
||||
|
@ -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 indented"><span class="csTitle">Enter:</span> Create topic</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>
|
||||
|
||||
|
@ -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"><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"><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>
|
||||
|
||||
|
@ -104,7 +104,7 @@
|
|||
<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 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>
|
||||
|
||||
|
@ -153,43 +153,21 @@
|
|||
<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>
|
||||
<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>
|
||||
<li>
|
||||
<a href="http://metamapscc.uservoice.com/knowledgebase/topics/61031-getting-started" target="_blank">Getting Started</a>
|
||||
</li>
|
||||
<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>
|
||||
<li><a href="https://docs.metamaps.cc/getting_started.html" target="_blank">Getting Started</a></li>
|
||||
<li><a href="https://docs.metamaps.cc/best_practices.html" target="_blank">Best Practices</a></li>
|
||||
<li><a href="https://docs.metamaps.cc/applications_and_use_cases.html" target="_blank">Applications & Use Cases</a></li>
|
||||
<li><a href="https://docs.metamaps.cc/advanced_features.html" target="_blank">Advanced Features</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<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>
|
||||
<li>
|
||||
<a href="http://metamapscc.uservoice.com/knowledgebase/topics/63440-general-questions" target="_blank">General Questions</a>
|
||||
</li>
|
||||
<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>
|
||||
<li><a href="https://docs.metamaps.cc/general_questions.html" target="_blank">General Questions</a></li>
|
||||
<li><a href="https://docs.metamaps.cc/project_organization_and_governance.html" target="_blank">Organization & Governance</a></li>
|
||||
<li><a href="https://docs.metamaps.cc/realtime_collaboration_junto.html" target="_blank">Realtime Collaboration</a></li>
|
||||
<li><a href="https://docs.metamaps.cc/importing_and_exporting_data.html" target="_blank">Importing and Exporting Data</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -2,8 +2,16 @@
|
|||
|
||||
# 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
|
||||
npm install
|
||||
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
|
||||
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
|
||||
config.assets.debug = false
|
||||
|
|
|
@ -30,6 +30,7 @@ Metamaps::Application.configure do
|
|||
# The :test delivery method accumulates sent emails in the
|
||||
# ActionMailer::Base.deliveries array.
|
||||
config.action_mailer.delivery_method = :test
|
||||
config.action_mailer.default_url_options = { host: 'localhost:3000' }
|
||||
|
||||
# Print deprecation notices to the 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.
|
||||
resource_owner_authenticator do
|
||||
current_user || redirect_to(new_user_session_url)
|
||||
current_user || redirect_to(sign_in_url)
|
||||
end
|
||||
|
||||
# If you want to restrict access to the web interface for adding oauth authorized applications,
|
||||
# you need to declare the block below.
|
||||
admin_authenticator do
|
||||
current_user || redirect_to(new_user_session_url)
|
||||
current_user || redirect_to(sign_in_url)
|
||||
end
|
||||
|
||||
# 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
|
||||
post 'events/:event', action: :events
|
||||
get :contains
|
||||
post :access, default: { format: :json }
|
||||
post :star, to: 'stars#create', defaults: { format: :json }
|
||||
post :unstar, to: 'stars#destroy', defaults: { format: :json }
|
||||
|
||||
get :request_access, to: 'access#request_access'
|
||||
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
|
||||
|
||||
|
@ -54,6 +62,19 @@ Metamaps::Application.routes.draw do
|
|||
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
|
||||
member do
|
||||
get :details
|
||||
|
@ -70,38 +91,18 @@ Metamaps::Application.routes.draw do
|
|||
delete :stars, to: 'stars#destroy', on: :member
|
||||
end
|
||||
resources :synapses, only: [:index, :create, :show, :update, :destroy]
|
||||
resources :tokens, only: [:create, :destroy] do
|
||||
get :my_tokens, on: :collection
|
||||
end
|
||||
resources :tokens, only: [:index, :create, :destroy]
|
||||
resources :topics, only: [:index, :create, :show, :update, :destroy]
|
||||
resources :users, only: [:index, :show] do
|
||||
get :current, on: :collection
|
||||
end
|
||||
match '*path', to: 'restful#catch_404', via: :all
|
||||
end
|
||||
namespace :v1, path: '/v1' do
|
||||
# api v1 routes all lead to a deprecation error method
|
||||
# see app/controllers/api/v1/deprecated_controller.rb
|
||||
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
|
||||
root to: 'deprecated#deprecated', via: :all
|
||||
match '*path', to: 'deprecated#deprecated', via: :all
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
match '*path', to: 'v2/restful#catch_404', via: :all
|
||||
end
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
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|
|
||||
t.integer "priority", 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.integer "user_id"
|
||||
t.integer "map_id"
|
||||
t.integer "sequence_id"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
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 ["sequence_id"], name: "index_events_on_sequence_id", using: :btree
|
||||
t.index ["user_id"], name: "index_events_on_user_id", using: :btree
|
||||
end
|
||||
|
||||
|
@ -67,6 +75,7 @@ ActiveRecord::Schema.define(version: 20160928022635) do
|
|||
t.datetime "updated_at", null: false
|
||||
t.integer "mappable_id"
|
||||
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", "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
|
||||
|
@ -75,16 +84,16 @@ ActiveRecord::Schema.define(version: 20160928022635) do
|
|||
end
|
||||
|
||||
create_table "maps", force: :cascade do |t|
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.text "name"
|
||||
t.boolean "arranged"
|
||||
t.text "desc"
|
||||
t.text "permission"
|
||||
t.integer "user_id"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.boolean "arranged"
|
||||
t.boolean "featured"
|
||||
t.string "screenshot_file_name"
|
||||
t.string "screenshot_content_type"
|
||||
t.string "screenshot_file_name", limit: 255
|
||||
t.string "screenshot_content_type", limit: 255
|
||||
t.integer "screenshot_file_size"
|
||||
t.datetime "screenshot_updated_at"
|
||||
t.index ["user_id"], name: "index_maps_on_user_id", using: :btree
|
||||
|
@ -103,21 +112,21 @@ ActiveRecord::Schema.define(version: 20160928022635) do
|
|||
end
|
||||
|
||||
create_table "metacode_sets", force: :cascade do |t|
|
||||
t.string "name"
|
||||
t.string "name", limit: 255
|
||||
t.text "desc"
|
||||
t.integer "user_id"
|
||||
t.boolean "mapperContributed"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["user_id"], name: "index_metacode_sets_on_user_id", using: :btree
|
||||
end
|
||||
|
||||
create_table "metacodes", force: :cascade do |t|
|
||||
t.text "name"
|
||||
t.string "manual_icon"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.string "color"
|
||||
t.string "manual_icon", limit: 255
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.string "color", limit: 255
|
||||
t.string "aws_icon_file_name"
|
||||
t.string "aws_icon_content_type"
|
||||
t.integer "aws_icon_file_size"
|
||||
|
@ -173,14 +182,15 @@ ActiveRecord::Schema.define(version: 20160928022635) do
|
|||
create_table "synapses", force: :cascade do |t|
|
||||
t.text "desc"
|
||||
t.text "category"
|
||||
t.text "weight"
|
||||
t.text "permission"
|
||||
t.integer "topic1_id"
|
||||
t.integer "topic2_id"
|
||||
t.integer "user_id"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.text "permission"
|
||||
t.text "weight"
|
||||
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"], 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
|
||||
|
@ -201,20 +211,21 @@ ActiveRecord::Schema.define(version: 20160928022635) do
|
|||
t.text "name"
|
||||
t.text "desc"
|
||||
t.text "link"
|
||||
t.text "permission"
|
||||
t.integer "user_id"
|
||||
t.integer "metacode_id"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.string "image_file_name"
|
||||
t.string "image_content_type"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.text "permission"
|
||||
t.string "image_file_name", limit: 255
|
||||
t.string "image_content_type", limit: 255
|
||||
t.integer "image_file_size"
|
||||
t.datetime "image_updated_at"
|
||||
t.string "audio_file_name"
|
||||
t.string "audio_content_type"
|
||||
t.string "audio_file_name", limit: 255
|
||||
t.string "audio_content_type", limit: 255
|
||||
t.integer "audio_file_size"
|
||||
t.datetime "audio_updated_at"
|
||||
t.integer "defer_to_map_id"
|
||||
t.boolean "in_trash"
|
||||
t.index ["metacode_id"], name: "index_topics_on_metacode_id", using: :btree
|
||||
t.index ["user_id"], name: "index_topics_on_user_id", using: :btree
|
||||
end
|
||||
|
@ -229,30 +240,30 @@ ActiveRecord::Schema.define(version: 20160928022635) do
|
|||
end
|
||||
|
||||
create_table "users", force: :cascade do |t|
|
||||
t.string "name"
|
||||
t.string "email"
|
||||
t.text "settings"
|
||||
t.string "code", limit: 8
|
||||
t.string "joinedwithcode", limit: 8
|
||||
t.string "crypted_password"
|
||||
t.string "password_salt"
|
||||
t.string "persistence_token"
|
||||
t.string "perishable_token"
|
||||
t.string "name", limit: 255
|
||||
t.string "email", limit: 255
|
||||
t.string "crypted_password", limit: 255
|
||||
t.string "password_salt", limit: 255
|
||||
t.string "persistence_token", limit: 255
|
||||
t.string "perishable_token", limit: 255
|
||||
t.datetime "created_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 "remember_token"
|
||||
t.string "remember_token", limit: 255
|
||||
t.datetime "remember_created_at"
|
||||
t.string "reset_password_token"
|
||||
t.string "reset_password_token", limit: 255
|
||||
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.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.boolean "admin"
|
||||
t.string "image_file_name"
|
||||
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.datetime "image_updated_at"
|
||||
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
|
||||
end
|
||||
|
||||
add_foreign_key "access_requests", "maps"
|
||||
add_foreign_key "access_requests", "users"
|
||||
add_foreign_key "tokens", "users"
|
||||
end
|
||||
|
|
|
@ -1,13 +1,19 @@
|
|||
#%RAML 1.0
|
||||
---
|
||||
title: Metamaps
|
||||
version: v2.0
|
||||
version: 2.0
|
||||
baseUri: https://metamaps.cc/api/v2
|
||||
mediaType: application/json
|
||||
protocols: [ HTTPS ]
|
||||
documentation:
|
||||
- title: Getting Started
|
||||
content: !include pages/getting-started.md
|
||||
|
||||
securitySchemes:
|
||||
cookie: !include securitySchemes/cookie.raml
|
||||
token: !include securitySchemes/token.raml
|
||||
oauth_2_0: !include securitySchemes/oauth_2_0.raml
|
||||
securedBy: [ oauth_2_0 ]
|
||||
securedBy: [ cookie, token, oauth_2_0 ]
|
||||
|
||||
traits:
|
||||
pageable: !include traits/pageable.raml
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#type: collection
|
||||
securedBy: [ null, cookie, token, oauth_2_0 ]
|
||||
get:
|
||||
is: [ searchable: { searchFields: "name" }, orderable, pageable ]
|
||||
responses:
|
||||
|
@ -7,6 +8,7 @@ get:
|
|||
application/json:
|
||||
example: !include ../examples/metacodes.json
|
||||
/{id}:
|
||||
securedBy: [ null, cookie, token, oauth_2_0 ]
|
||||
#type: item
|
||||
get:
|
||||
responses:
|
||||
|
|
|
@ -1,4 +1,13 @@
|
|||
#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:
|
||||
body:
|
||||
application/json:
|
||||
|
@ -11,14 +20,6 @@ post:
|
|||
body:
|
||||
application/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}:
|
||||
#type: item
|
||||
delete:
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#type: collection
|
||||
securedBy: [ null, cookie, token, oauth_2_0 ]
|
||||
get:
|
||||
is: [ searchable: { searchFields: "name" }, orderable, pageable ]
|
||||
responses:
|
||||
|
@ -6,6 +7,15 @@ get:
|
|||
body:
|
||||
application/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:
|
||||
#type: item
|
||||
get:
|
||||
|
@ -14,11 +24,3 @@ get:
|
|||
body:
|
||||
application/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: |
|
||||
OAuth 2.0 implementation
|
||||
description: !include ../pages/oauth_2_0_tutorial.md
|
||||
type: OAuth 2.0
|
||||
settings:
|
||||
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)
|
||||
},
|
||||
savedEvent: function () {
|
||||
Realtime.sendMapChange(this)
|
||||
Realtime.updateMap(this)
|
||||
},
|
||||
authorizeToEdit: function (mapper) {
|
||||
if (mapper && (
|
||||
|
@ -370,7 +370,7 @@ _Backbone.init = function () {
|
|||
return node
|
||||
},
|
||||
savedEvent: function () {
|
||||
Realtime.sendTopicChange(this)
|
||||
Realtime.updateTopic(this)
|
||||
},
|
||||
updateViews: function () {
|
||||
var onPageWithTopicCard = Active.Map || Active.Topic
|
||||
|
@ -549,7 +549,7 @@ _Backbone.init = function () {
|
|||
return edge
|
||||
},
|
||||
savedEvent: function () {
|
||||
Realtime.sendSynapseChange(this)
|
||||
Realtime.updateSynapse(this)
|
||||
},
|
||||
updateViews: function () {
|
||||
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, $ */
|
||||
|
||||
import clipboard from 'clipboard-js'
|
||||
|
||||
import Active from '../Active'
|
||||
import Create from '../Create'
|
||||
|
||||
import Search from './Search'
|
||||
import CreateMap from './CreateMap'
|
||||
import Account from './Account'
|
||||
import ImportDialog from './ImportDialog'
|
||||
|
||||
/*
|
||||
* Metamaps.Backbone
|
||||
|
@ -21,6 +24,7 @@ const GlobalUI = {
|
|||
self.Search.init()
|
||||
self.CreateMap.init()
|
||||
self.Account.init()
|
||||
self.ImportDialog.init(Metamaps.Erb, self.openLightbox, self.closeLightbox)
|
||||
|
||||
if ($('#toast').html().trim()) self.notifyUser($('#toast').html())
|
||||
|
||||
|
@ -137,9 +141,19 @@ const GlobalUI = {
|
|||
self.hideDiv('#toast')
|
||||
},
|
||||
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
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue