Merge branch 'develop' for v3.0
1
.babelrc
|
@ -4,6 +4,7 @@
|
|||
"es2015"
|
||||
],
|
||||
"plugins": [
|
||||
"lodash",
|
||||
"transform-class-properties"
|
||||
]
|
||||
}
|
||||
|
|
35
.codeclimate.yml
Normal file
|
@ -0,0 +1,35 @@
|
|||
---
|
||||
engines:
|
||||
brakeman:
|
||||
enabled: true
|
||||
bundler-audit:
|
||||
enabled: true
|
||||
duplication:
|
||||
enabled: true
|
||||
config:
|
||||
languages:
|
||||
ruby:
|
||||
mass_threshold: 36 # default: 18
|
||||
javascript:
|
||||
mass_threshold: 80 # default: 40
|
||||
eslint:
|
||||
enabled: true
|
||||
channel: "eslint-3"
|
||||
fixme:
|
||||
enabled: true
|
||||
rubocop:
|
||||
enabled: true
|
||||
ratings:
|
||||
paths:
|
||||
- 'Gemfile.lock'
|
||||
- '**.erb'
|
||||
- '**.rb'
|
||||
- '**.js'
|
||||
- '**.jsx'
|
||||
exclude_paths:
|
||||
- app/assets/images/
|
||||
- app/assets/javascripts/lib/
|
||||
- frontend/src/patched/
|
||||
- db/
|
||||
- script/
|
||||
- spec/
|
3
.eslintignore
Normal file
|
@ -0,0 +1,3 @@
|
|||
**/*{.,-}min.js
|
||||
frontend/src/patched/*
|
||||
app/assets/javascripts/lib/*
|
25
.eslintrc.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
module.exports = {
|
||||
"sourceType": "module",
|
||||
"parser": "babel-eslint",
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
}
|
||||
},
|
||||
"extends": "standard",
|
||||
"installedESLint": true,
|
||||
"env": {
|
||||
"es6": true,
|
||||
"node": true
|
||||
},
|
||||
"plugins": [
|
||||
"promise",
|
||||
"standard",
|
||||
"react"
|
||||
],
|
||||
"rules": {
|
||||
"react/jsx-uses-react": [2],
|
||||
"react/jsx-uses-vars": [2],
|
||||
"yoda": [2, "never", { "exceptRange": true }]
|
||||
}
|
||||
}
|
|
@ -14,10 +14,10 @@ export SECRET_KEY_BASE='267c8a84f63963282f45bc3010eaddf027abfab58fc759d6e239c800
|
|||
# # you can safely leave these blank, unless you're deploying an instance, in
|
||||
# # which case you'll need to set them up
|
||||
#
|
||||
# export S3_REGION
|
||||
# export S3_BUCKET_NAME
|
||||
# export AWS_ACCESS_KEY_ID
|
||||
# export AWS_SECRET_ACCESS_KEY
|
||||
# export SSO_KEY
|
||||
#
|
||||
# export SMTP_DOMAIN
|
||||
# export SMTP_PASSWORD
|
||||
|
|
1
.gitignore
vendored
|
@ -7,6 +7,7 @@
|
|||
#assety stuff
|
||||
public/assets
|
||||
public/metamaps_mobile
|
||||
public/api/index.html
|
||||
vendor/
|
||||
node_modules
|
||||
npm-debug.log
|
||||
|
|
|
@ -6,9 +6,16 @@ AllCops:
|
|||
- 'bin/**/*'
|
||||
- 'vendor/**/*'
|
||||
- 'app/assets/javascripts/node_modules/**/*'
|
||||
- 'Vagrantfile'
|
||||
|
||||
Rails:
|
||||
Enabled: true
|
||||
|
||||
Metrics/LineLength:
|
||||
Max: 100
|
||||
|
||||
Metrics/AbcSize:
|
||||
Max: 16
|
||||
|
||||
Style/Documentation:
|
||||
Enabled: false
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -16,6 +16,9 @@ before_script:
|
|||
- . $HOME/.nvm/nvm.sh
|
||||
- nvm install stable
|
||||
- nvm use stable
|
||||
- npm install
|
||||
- npm install --no-optional
|
||||
script:
|
||||
- bundle exec rspec && npm test && bundle exec brakeman -q -z
|
||||
- bundle exec rspec && bundle exec brakeman -q -z && npm test
|
||||
addons:
|
||||
code_climate:
|
||||
repo_token: 479d3bf56798fbc7fff3fc8151a5ed09e8ac368fd5af332c437b9e07dbebb44e
|
||||
|
|
37
Gemfile
|
@ -1,47 +1,37 @@
|
|||
# frozen_string_literal: true
|
||||
source 'https://rubygems.org'
|
||||
ruby '2.3.0'
|
||||
|
||||
gem 'rails'
|
||||
gem 'rails', '~> 5.0.0'
|
||||
|
||||
gem 'active_model_serializers', '~> 0.8.1'
|
||||
gem 'aws-sdk', '< 2.0'
|
||||
gem 'best_in_place' # in-place editing
|
||||
gem 'delayed_job', '~> 4.0.2'
|
||||
gem 'delayed_job_active_record', '~> 4.0.1'
|
||||
gem 'active_model_serializers'
|
||||
gem 'aws-sdk'
|
||||
gem 'best_in_place'
|
||||
gem 'delayed_job'
|
||||
gem 'delayed_job_active_record'
|
||||
gem 'sucker_punch'
|
||||
gem 'devise'
|
||||
gem 'doorkeeper'
|
||||
gem 'dotenv-rails'
|
||||
gem 'exception_notification'
|
||||
gem 'formtastic'
|
||||
gem 'formula'
|
||||
gem 'httparty'
|
||||
gem 'json'
|
||||
gem 'kaminari' # pagination
|
||||
gem 'kaminari'
|
||||
gem 'paperclip'
|
||||
gem 'pg'
|
||||
gem 'pundit'
|
||||
gem 'pundit_extra'
|
||||
gem 'rack-attack'
|
||||
gem 'rack-cors'
|
||||
gem 'rails3-jquery-autocomplete'
|
||||
gem 'redis'
|
||||
gem 'slack-notifier'
|
||||
gem 'snorlax'
|
||||
gem 'uservoice-ruby'
|
||||
|
||||
# asset stuff
|
||||
gem 'jquery-rails'
|
||||
gem 'jquery-ui-rails'
|
||||
gem 'jbuilder'
|
||||
|
||||
group :assets do
|
||||
gem 'coffee-rails'
|
||||
gem 'sass-rails'
|
||||
gem 'uglifier'
|
||||
# gem 'therubyracer'
|
||||
end
|
||||
|
||||
group :production do
|
||||
gem 'rails_12factor'
|
||||
end
|
||||
gem 'sass-rails'
|
||||
gem 'uglifier'
|
||||
|
||||
group :test do
|
||||
gem 'factory_girl_rails'
|
||||
|
@ -57,7 +47,6 @@ group :development, :test do
|
|||
gem 'binding_of_caller'
|
||||
gem 'pry-byebug'
|
||||
gem 'pry-rails'
|
||||
gem 'quiet_assets'
|
||||
gem 'tunemygc'
|
||||
gem 'rubocop'
|
||||
end
|
||||
|
|
286
Gemfile.lock
|
@ -1,51 +1,57 @@
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actionmailer (4.2.6)
|
||||
actionpack (= 4.2.6)
|
||||
actionview (= 4.2.6)
|
||||
activejob (= 4.2.6)
|
||||
actioncable (5.0.0.1)
|
||||
actionpack (= 5.0.0.1)
|
||||
nio4r (~> 1.2)
|
||||
websocket-driver (~> 0.6.1)
|
||||
actionmailer (5.0.0.1)
|
||||
actionpack (= 5.0.0.1)
|
||||
actionview (= 5.0.0.1)
|
||||
activejob (= 5.0.0.1)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 1.0, >= 1.0.5)
|
||||
actionpack (4.2.6)
|
||||
actionview (= 4.2.6)
|
||||
activesupport (= 4.2.6)
|
||||
rack (~> 1.6)
|
||||
rack-test (~> 0.6.2)
|
||||
rails-dom-testing (~> 1.0, >= 1.0.5)
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (5.0.0.1)
|
||||
actionview (= 5.0.0.1)
|
||||
activesupport (= 5.0.0.1)
|
||||
rack (~> 2.0)
|
||||
rack-test (~> 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||
actionview (4.2.6)
|
||||
activesupport (= 4.2.6)
|
||||
actionview (5.0.0.1)
|
||||
activesupport (= 5.0.0.1)
|
||||
builder (~> 3.1)
|
||||
erubis (~> 2.7.0)
|
||||
rails-dom-testing (~> 1.0, >= 1.0.5)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||
active_model_serializers (0.8.3)
|
||||
activemodel (>= 3.0)
|
||||
activejob (4.2.6)
|
||||
activesupport (= 4.2.6)
|
||||
globalid (>= 0.3.0)
|
||||
activemodel (4.2.6)
|
||||
activesupport (= 4.2.6)
|
||||
builder (~> 3.1)
|
||||
activerecord (4.2.6)
|
||||
activemodel (= 4.2.6)
|
||||
activesupport (= 4.2.6)
|
||||
arel (~> 6.0)
|
||||
activesupport (4.2.6)
|
||||
active_model_serializers (0.10.2)
|
||||
actionpack (>= 4.1, < 6)
|
||||
activemodel (>= 4.1, < 6)
|
||||
jsonapi (~> 0.1.1.beta2)
|
||||
railties (>= 4.1, < 6)
|
||||
activejob (5.0.0.1)
|
||||
activesupport (= 5.0.0.1)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (5.0.0.1)
|
||||
activesupport (= 5.0.0.1)
|
||||
activerecord (5.0.0.1)
|
||||
activemodel (= 5.0.0.1)
|
||||
activesupport (= 5.0.0.1)
|
||||
arel (~> 7.0)
|
||||
activesupport (5.0.0.1)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (~> 0.7)
|
||||
json (~> 1.7, >= 1.7.7)
|
||||
minitest (~> 5.1)
|
||||
thread_safe (~> 0.3, >= 0.3.4)
|
||||
tzinfo (~> 1.1)
|
||||
addressable (2.3.8)
|
||||
arel (6.0.3)
|
||||
arel (7.1.2)
|
||||
ast (2.3.0)
|
||||
aws-sdk (1.66.0)
|
||||
aws-sdk-v1 (= 1.66.0)
|
||||
aws-sdk-v1 (1.66.0)
|
||||
json (~> 1.4)
|
||||
nokogiri (>= 1.4.4)
|
||||
aws-sdk (2.6.3)
|
||||
aws-sdk-resources (= 2.6.3)
|
||||
aws-sdk-core (2.6.3)
|
||||
jmespath (~> 1.0)
|
||||
aws-sdk-resources (2.6.3)
|
||||
aws-sdk-core (= 2.6.3)
|
||||
bcrypt (3.1.11)
|
||||
best_in_place (3.1.0)
|
||||
actionpack (>= 3.2)
|
||||
|
@ -56,7 +62,7 @@ GEM
|
|||
rack (>= 0.9.0)
|
||||
binding_of_caller (0.7.2)
|
||||
debug_inspector (>= 0.0.1)
|
||||
brakeman (3.3.2)
|
||||
brakeman (3.4.0)
|
||||
builder (3.2.2)
|
||||
byebug (9.0.5)
|
||||
climate_control (0.0.3)
|
||||
|
@ -64,21 +70,14 @@ GEM
|
|||
cocaine (0.5.8)
|
||||
climate_control (>= 0.0.3, < 1.0)
|
||||
coderay (1.1.1)
|
||||
coffee-rails (4.1.1)
|
||||
coffee-script (>= 2.2.0)
|
||||
railties (>= 4.0.0, < 5.1.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.0.6)
|
||||
activesupport (>= 3.0, < 5.0)
|
||||
delayed_job_active_record (4.0.3)
|
||||
activerecord (>= 3.0, < 5.0)
|
||||
delayed_job (>= 3.0, < 4.1)
|
||||
devise (4.1.1)
|
||||
delayed_job (4.1.2)
|
||||
activesupport (>= 3.0, < 5.1)
|
||||
delayed_job_active_record (4.1.1)
|
||||
activerecord (>= 3.0, < 5.1)
|
||||
delayed_job (>= 3.0, < 5)
|
||||
devise (4.2.0)
|
||||
bcrypt (~> 3.0)
|
||||
orm_adapter (~> 0.1)
|
||||
railties (>= 4.1.0, < 5.1)
|
||||
|
@ -86,37 +85,29 @@ GEM
|
|||
warden (~> 1.2.3)
|
||||
diff-lcs (1.2.5)
|
||||
docile (1.1.5)
|
||||
doorkeeper (3.1.0)
|
||||
railties (>= 3.2)
|
||||
doorkeeper (4.2.0)
|
||||
railties (>= 4.2)
|
||||
dotenv (2.1.1)
|
||||
dotenv-rails (2.1.1)
|
||||
dotenv (= 2.1.1)
|
||||
railties (>= 4.0, < 5.1)
|
||||
erubis (2.7.0)
|
||||
exception_notification (4.1.4)
|
||||
actionmailer (~> 4.0)
|
||||
activesupport (~> 4.0)
|
||||
exception_notification (4.2.1)
|
||||
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)
|
||||
factory_girl (~> 4.7.0)
|
||||
railties (>= 3.0.0)
|
||||
formtastic (3.1.4)
|
||||
actionpack (>= 3.2.13)
|
||||
formula (1.1.1)
|
||||
rails (> 3.0.0)
|
||||
globalid (0.3.6)
|
||||
globalid (0.3.7)
|
||||
activesupport (>= 4.1.0)
|
||||
httparty (0.13.7)
|
||||
json (~> 1.8)
|
||||
httparty (0.14.0)
|
||||
multi_xml (>= 0.5.2)
|
||||
i18n (0.7.0)
|
||||
jbuilder (2.5.0)
|
||||
activesupport (>= 3.0.0, < 5.1)
|
||||
multi_json (~> 1.2)
|
||||
jquery-rails (4.1.1)
|
||||
jmespath (1.3.1)
|
||||
jquery-rails (4.2.1)
|
||||
rails-dom-testing (>= 1, < 3)
|
||||
railties (>= 4.2.0)
|
||||
thor (>= 0.14, < 2.0)
|
||||
|
@ -125,6 +116,8 @@ GEM
|
|||
json (1.8.3)
|
||||
json-schema (2.6.2)
|
||||
addressable (~> 2.3.8)
|
||||
jsonapi (0.1.1.beta2)
|
||||
json (~> 1.8)
|
||||
kaminari (0.17.0)
|
||||
actionpack (>= 3.0.0)
|
||||
activesupport (>= 3.0.0)
|
||||
|
@ -136,28 +129,27 @@ GEM
|
|||
mime-types (3.1)
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2016.0521)
|
||||
mimemagic (0.3.0)
|
||||
mimemagic (0.3.2)
|
||||
mini_portile2 (2.1.0)
|
||||
minitest (5.9.0)
|
||||
multi_json (1.12.1)
|
||||
minitest (5.9.1)
|
||||
multi_xml (0.5.5)
|
||||
nio4r (1.2.1)
|
||||
nokogiri (1.6.8)
|
||||
mini_portile2 (~> 2.1.0)
|
||||
pkg-config (~> 1.1.7)
|
||||
oauth (0.5.1)
|
||||
orm_adapter (0.5.0)
|
||||
paperclip (4.3.6)
|
||||
activemodel (>= 3.2.0)
|
||||
activesupport (>= 3.2.0)
|
||||
paperclip (5.1.0)
|
||||
activemodel (>= 4.2.0)
|
||||
activesupport (>= 4.2.0)
|
||||
cocaine (~> 0.5.5)
|
||||
mime-types
|
||||
mimemagic (= 0.3.0)
|
||||
parser (2.3.1.2)
|
||||
mimemagic (~> 0.3.0)
|
||||
parser (2.3.1.4)
|
||||
ast (~> 2.2)
|
||||
pg (0.18.4)
|
||||
pg (0.19.0)
|
||||
pkg-config (1.1.7)
|
||||
powerpack (0.1.1)
|
||||
pry (0.10.3)
|
||||
pry (0.10.4)
|
||||
coderay (~> 1.1.0)
|
||||
method_source (~> 0.8.1)
|
||||
slop (~> 3.4)
|
||||
|
@ -168,67 +160,59 @@ GEM
|
|||
pry (>= 0.9.10)
|
||||
pundit (1.1.0)
|
||||
activesupport (>= 3.0.0)
|
||||
pundit_extra (0.2.0)
|
||||
quiet_assets (1.1.0)
|
||||
railties (>= 3.1, < 5.0)
|
||||
rack (1.6.4)
|
||||
pundit_extra (0.3.0)
|
||||
rack (2.0.1)
|
||||
rack-attack (5.0.1)
|
||||
rack
|
||||
rack-cors (0.4.0)
|
||||
rack-test (0.6.3)
|
||||
rack (>= 1.0)
|
||||
rails (4.2.6)
|
||||
actionmailer (= 4.2.6)
|
||||
actionpack (= 4.2.6)
|
||||
actionview (= 4.2.6)
|
||||
activejob (= 4.2.6)
|
||||
activemodel (= 4.2.6)
|
||||
activerecord (= 4.2.6)
|
||||
activesupport (= 4.2.6)
|
||||
rails (5.0.0.1)
|
||||
actioncable (= 5.0.0.1)
|
||||
actionmailer (= 5.0.0.1)
|
||||
actionpack (= 5.0.0.1)
|
||||
actionview (= 5.0.0.1)
|
||||
activejob (= 5.0.0.1)
|
||||
activemodel (= 5.0.0.1)
|
||||
activerecord (= 5.0.0.1)
|
||||
activesupport (= 5.0.0.1)
|
||||
bundler (>= 1.3.0, < 2.0)
|
||||
railties (= 4.2.6)
|
||||
sprockets-rails
|
||||
rails-deprecated_sanitizer (1.0.3)
|
||||
activesupport (>= 4.2.0.alpha)
|
||||
rails-dom-testing (1.0.7)
|
||||
activesupport (>= 4.2.0.beta, < 5.0)
|
||||
railties (= 5.0.0.1)
|
||||
sprockets-rails (>= 2.0.0)
|
||||
rails-dom-testing (2.0.1)
|
||||
activesupport (>= 4.2.0, < 6.0)
|
||||
nokogiri (~> 1.6.0)
|
||||
rails-deprecated_sanitizer (>= 1.0.1)
|
||||
rails-html-sanitizer (1.0.3)
|
||||
loofah (~> 2.0)
|
||||
rails3-jquery-autocomplete (1.0.15)
|
||||
rails (>= 3.2)
|
||||
rails_12factor (0.0.3)
|
||||
rails_serve_static_assets
|
||||
rails_stdout_logging
|
||||
rails_serve_static_assets (0.0.5)
|
||||
rails_stdout_logging (0.0.5)
|
||||
railties (4.2.6)
|
||||
actionpack (= 4.2.6)
|
||||
activesupport (= 4.2.6)
|
||||
railties (5.0.0.1)
|
||||
actionpack (= 5.0.0.1)
|
||||
activesupport (= 5.0.0.1)
|
||||
method_source
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.18.1, < 2.0)
|
||||
rainbow (2.1.0)
|
||||
rake (11.2.2)
|
||||
redis (3.3.0)
|
||||
responders (2.2.0)
|
||||
rake (11.3.0)
|
||||
redis (3.3.1)
|
||||
responders (2.3.0)
|
||||
railties (>= 4.2.0, < 5.1)
|
||||
rspec-core (3.4.4)
|
||||
rspec-support (~> 3.4.0)
|
||||
rspec-expectations (3.4.0)
|
||||
rspec-core (3.5.3)
|
||||
rspec-support (~> 3.5.0)
|
||||
rspec-expectations (3.5.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.4.0)
|
||||
rspec-mocks (3.4.1)
|
||||
rspec-support (~> 3.5.0)
|
||||
rspec-mocks (3.5.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.4.0)
|
||||
rspec-rails (3.4.2)
|
||||
actionpack (>= 3.0, < 4.3)
|
||||
activesupport (>= 3.0, < 4.3)
|
||||
railties (>= 3.0, < 4.3)
|
||||
rspec-core (~> 3.4.0)
|
||||
rspec-expectations (~> 3.4.0)
|
||||
rspec-mocks (~> 3.4.0)
|
||||
rspec-support (~> 3.4.0)
|
||||
rspec-support (3.4.1)
|
||||
rubocop (0.41.1)
|
||||
rspec-support (~> 3.5.0)
|
||||
rspec-rails (3.5.2)
|
||||
actionpack (>= 3.0)
|
||||
activesupport (>= 3.0)
|
||||
railties (>= 3.0)
|
||||
rspec-core (~> 3.5.0)
|
||||
rspec-expectations (~> 3.5.0)
|
||||
rspec-mocks (~> 3.5.0)
|
||||
rspec-support (~> 3.5.0)
|
||||
rspec-support (3.5.0)
|
||||
rubocop (0.43.0)
|
||||
parser (>= 2.3.1.1, < 3.0)
|
||||
powerpack (~> 0.1)
|
||||
rainbow (>= 1.99.1, < 3.0)
|
||||
|
@ -236,68 +220,65 @@ GEM
|
|||
unicode-display_width (~> 1.0, >= 1.0.1)
|
||||
ruby-progressbar (1.8.1)
|
||||
sass (3.4.22)
|
||||
sass-rails (5.0.4)
|
||||
railties (>= 4.0.0, < 5.0)
|
||||
sass-rails (5.0.6)
|
||||
railties (>= 4.0.0, < 6)
|
||||
sass (~> 3.1)
|
||||
sprockets (>= 2.8, < 4.0)
|
||||
sprockets-rails (>= 2.0, < 4.0)
|
||||
tilt (>= 1.1, < 3)
|
||||
shoulda-matchers (3.1.1)
|
||||
activesupport (>= 4.0.0)
|
||||
simplecov (0.11.2)
|
||||
simplecov (0.12.0)
|
||||
docile (~> 1.1.0)
|
||||
json (~> 1.8)
|
||||
json (>= 1.8, < 3)
|
||||
simplecov-html (~> 0.10.0)
|
||||
simplecov-html (0.10.0)
|
||||
slack-notifier (1.5.1)
|
||||
slop (3.6.0)
|
||||
snorlax (0.1.6)
|
||||
rails (> 4.1)
|
||||
sprockets (3.6.0)
|
||||
sprockets (3.7.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
rack (> 1, < 3)
|
||||
sprockets-rails (3.0.4)
|
||||
sprockets-rails (3.2.0)
|
||||
actionpack (>= 4.0)
|
||||
activesupport (>= 4.0)
|
||||
sprockets (>= 3.0.0)
|
||||
sucker_punch (2.0.2)
|
||||
concurrent-ruby (~> 1.0.0)
|
||||
thor (0.19.1)
|
||||
thread_safe (0.3.5)
|
||||
tilt (2.0.5)
|
||||
tunemygc (1.0.65)
|
||||
tunemygc (1.0.68)
|
||||
tzinfo (1.2.2)
|
||||
thread_safe (~> 0.1)
|
||||
uglifier (3.0.0)
|
||||
uglifier (3.0.2)
|
||||
execjs (>= 0.3.0, < 3)
|
||||
unicode-display_width (1.1.0)
|
||||
uservoice-ruby (0.0.11)
|
||||
ezcrypto (>= 0.7.2)
|
||||
json (>= 1.7.5)
|
||||
oauth (>= 0.4.7)
|
||||
unicode-display_width (1.1.1)
|
||||
warden (1.2.6)
|
||||
rack (>= 1.0)
|
||||
websocket-driver (0.6.4)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.2)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
active_model_serializers (~> 0.8.1)
|
||||
aws-sdk (< 2.0)
|
||||
active_model_serializers
|
||||
aws-sdk
|
||||
best_in_place
|
||||
better_errors
|
||||
binding_of_caller
|
||||
brakeman
|
||||
coffee-rails
|
||||
delayed_job (~> 4.0.2)
|
||||
delayed_job_active_record (~> 4.0.1)
|
||||
delayed_job
|
||||
delayed_job_active_record
|
||||
devise
|
||||
doorkeeper
|
||||
dotenv-rails
|
||||
exception_notification
|
||||
factory_girl_rails
|
||||
formtastic
|
||||
formula
|
||||
httparty
|
||||
jbuilder
|
||||
jquery-rails
|
||||
jquery-ui-rails
|
||||
json
|
||||
|
@ -309,11 +290,9 @@ DEPENDENCIES
|
|||
pry-rails
|
||||
pundit
|
||||
pundit_extra
|
||||
quiet_assets
|
||||
rack-attack
|
||||
rack-cors
|
||||
rails
|
||||
rails3-jquery-autocomplete
|
||||
rails_12factor
|
||||
rails (~> 5.0.0)
|
||||
redis
|
||||
rspec-rails
|
||||
rubocop
|
||||
|
@ -322,9 +301,12 @@ DEPENDENCIES
|
|||
simplecov
|
||||
slack-notifier
|
||||
snorlax
|
||||
sucker_punch
|
||||
tunemygc
|
||||
uglifier
|
||||
uservoice-ruby
|
||||
|
||||
RUBY VERSION
|
||||
ruby 2.3.0p0
|
||||
|
||||
BUNDLED WITH
|
||||
1.11.2
|
||||
1.13.6
|
||||
|
|
39
README.md
|
@ -1,20 +1,25 @@
|
|||
Metamaps
|
||||
=======
|
||||
|
||||
[![Join the chat at https://gitter.im/metamaps/metamaps](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/metamaps/metamaps?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[![Build Status](https://travis-ci.org/metamaps/metamaps.svg?branch=develop)](https://travis-ci.org/metamaps/metamaps)
|
||||
|
||||
Welcome to the Metamaps GitHub repo.
|
||||
## What is Metamaps?
|
||||
|
||||
## About
|
||||
Metamaps is a free and open-source technology for changemakers, innovators, educators and students. It enables individuals and communities to build and visualize their shared knowledge and unlock their collective intelligence.
|
||||
|
||||
Metamaps is a free and AGPL open source technology for changemakers, innovators, educators and students. It enables individuals and communities to build and visualize their shared knowledge and unlock their collective intelligence. You can find out about more about the project at the [blog][site-blog].
|
||||
You can find a version of this software running at [metamaps.cc][site-beta], where the technology is being tested in an open beta.
|
||||
|
||||
You can find a version of this software running at [metamaps.cc][site-beta], where the technology is being tested in a private beta.
|
||||
Metamaps is developed and maintained by a distributed, nomadic community comprised of technologists, artists and storytellers. You can get in touch by using whichever of these channels you prefer:
|
||||
|
||||
Metamaps is created and maintained by a distributed, nomadic community comprised of technologists, artists and storytellers. You can get in touch with us at team@metamaps.cc or @metamapps on twitter.
|
||||
|
||||
To get connected with the community interested in Metamaps, join our [Google+ community][community].
|
||||
## 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.
|
||||
|
||||
## Installation
|
||||
|
||||
|
@ -46,33 +51,21 @@ OR create a new account at `/join`, and use access code `qwertyui`
|
|||
|
||||
Start mapping and programming!
|
||||
|
||||
We haven't figured out Vagrant for Windows yet, but we have a set of manual instructions here:
|
||||
We haven't set up instructions for using Vagrant on Windows, but there are instructions for a manual setup here:
|
||||
|
||||
- [For Windows][windows-installation]
|
||||
|
||||
## Contributing
|
||||
|
||||
Cloning this repository directly is primarily for those wishing to contribute to our codebase. Check out our [contributing instructions][contributing] to get involved.
|
||||
|
||||
## Community
|
||||
|
||||
- If you would like to report a bug, please check the [issues][contributing-issues] section in our [contributing instructions][contributing].
|
||||
- To participate in discussions and a public forum about Metamaps, join the [Google+ community][community]
|
||||
- For contributors, read more instructions in [CONTRIBUTING.md][contributing].
|
||||
|
||||
## Licensing information
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
|
||||
|
||||
The license can be read [here][license].
|
||||
|
||||
Copyright (c) 2015 Connor Turland
|
||||
Copyright (c) 2016 Connor Turland
|
||||
|
||||
|
||||
[site-blog]: http://blog.metamaps.cc
|
||||
[site-beta]: http://metamaps.cc
|
||||
[community]: https://plus.google.com/u/0/communities/115060009262157699234
|
||||
[license]: https://github.com/metamaps/metamaps/blob/develop/LICENSE
|
||||
[contributing]: https://github.com/metamaps/metamaps/blob/develop/doc/CONTRIBUTING.md
|
||||
[contributing-issues]: https://github.com/metamaps/metamaps/blob/develop/doc/CONTRIBUTING.md#reporting-bugs-and-other-issues
|
||||
|
|
1
Rakefile
|
@ -1,4 +1,5 @@
|
|||
#!/usr/bin/env rake
|
||||
# frozen_string_literal: true
|
||||
# Add your own tasks in files placed in lib/tasks ending in .rake,
|
||||
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
|
||||
|
||||
|
|
2
Vagrantfile
vendored
|
@ -31,7 +31,7 @@ sudo -u postgres psql -c "ALTER USER postgres WITH PASSWORD '3112';"
|
|||
|
||||
SCRIPT
|
||||
|
||||
VAGRANTFILE_API_VERSION = '2'.freeze
|
||||
VAGRANTFILE_API_VERSION = '2'
|
||||
|
||||
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
||||
config.vm.box = 'trusty64'
|
||||
|
|
6
app/assets/config/manifest.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
// JS and CSS bundles
|
||||
//= link_directory ../javascripts .js
|
||||
//= link_directory ../stylesheets .css
|
||||
|
||||
// Other
|
||||
//= link_tree ../images
|
BIN
app/assets/images/.DS_Store
vendored
BIN
app/assets/images/import-example.png
Normal file
After Width: | Height: | Size: 44 KiB |
BIN
app/assets/images/junto.gif
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
app/assets/images/metadata.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
app/assets/images/pincarousel_sprite.png
Normal file
After Width: | Height: | Size: 822 B |
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 1.6 KiB |
BIN
app/assets/images/view-only.png
Normal file
After Width: | Height: | Size: 421 B |
|
@ -13,34 +13,6 @@
|
|||
//= require jquery
|
||||
//= require jquery-ui
|
||||
//= require jquery_ujs
|
||||
//= require ./webpacked/metamaps.bundle
|
||||
//= require_directory ./lib
|
||||
//= require ./src/Metamaps.GlobalUI
|
||||
//= require ./src/Metamaps.Router
|
||||
//= require ./src/Metamaps.Backbone
|
||||
//= require ./src/Metamaps.Views
|
||||
//= require ./src/views/chatView
|
||||
//= require ./src/views/videoView
|
||||
//= require ./src/views/room
|
||||
//= require ./src/JIT
|
||||
//= require ./src/check-canvas-support
|
||||
//= require ./src/Metamaps
|
||||
//= require ./src/Metamaps.Create
|
||||
//= require ./src/Metamaps.TopicCard
|
||||
//= require ./src/Metamaps.SynapseCard
|
||||
//= require ./src/Metamaps.Visualize
|
||||
//= require ./src/Metamaps.Util
|
||||
//= require ./src/Metamaps.Realtime
|
||||
//= require ./src/Metamaps.Control
|
||||
//= require ./src/Metamaps.Filter
|
||||
//= require ./src/Metamaps.Listeners
|
||||
//= require ./src/Metamaps.Organize
|
||||
//= require ./src/Metamaps.Topic
|
||||
//= require ./src/Metamaps.Synapse
|
||||
//= require ./src/Metamaps.Map
|
||||
//= require ./src/Metamaps.Account
|
||||
//= require ./src/Metamaps.Mapper
|
||||
//= require ./src/Metamaps.Mobile
|
||||
//= require ./src/Metamaps.Admin
|
||||
//= require ./src/Metamaps.Import
|
||||
//= require ./src/Metamaps.JIT
|
||||
//= require ./src/Metamaps.Erb
|
||||
//= require ./webpacked/metamaps.bundle
|
||||
|
|
|
@ -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
|
@ -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);
|
|
@ -156,43 +156,48 @@ jQuery.browser = browser;
|
|||
event.data.rotate(-1);
|
||||
return false;
|
||||
});
|
||||
$(options.buttonRight).bind('mouseup',this,function(event){
|
||||
$(options.buttonRight).bind('mouseup',this,function(event){
|
||||
event.data.rotate(1);
|
||||
return false;
|
||||
});
|
||||
|
||||
// START METAMAPS CODE
|
||||
// Add code that makes tab and shift+tab scroll through metacodes
|
||||
$('.new_topic').bind('keydown',this,function(event){
|
||||
if (event.keyCode == 9 && event.shiftKey) {
|
||||
event.data.rotate(-1);
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
} else if (event.keyCode == 9) {
|
||||
event.data.rotate(1);
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
if (event.keyCode == 9) {
|
||||
if (event.shiftKey) {
|
||||
event.data.rotate(-1)
|
||||
} else {
|
||||
event.data.rotate(1)
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
Metamaps.Create.newTopic.metacode = $(items[event.data.frontIndex].image).attr('data-id');
|
||||
}
|
||||
});
|
||||
// END METAMAPS CODE
|
||||
|
||||
// You will need this plugin for the mousewheel to work: http://plugins.jquery.com/project/mousewheel
|
||||
if (options.mouseWheel)
|
||||
{
|
||||
// START METAMAPS CODE
|
||||
$('body').bind('mousewheel',this,function(event, delta) {
|
||||
if (Metamaps.Create.newTopic.beingCreated && !Metamaps.Create.isSwitchingSet) {
|
||||
event.data.rotate(delta);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if (Metamaps.Create.newTopic.beingCreated &&
|
||||
!Metamaps.Create.isSwitchingSet &&
|
||||
!Metamaps.Create.newTopic.pinned) {
|
||||
event.data.rotate(delta);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
// END METAMAPS CODE
|
||||
/* ORIGINAL CODE
|
||||
$(container).bind('mousewheel',this,function(event, delta) {
|
||||
event.data.rotate(delta);
|
||||
return false;
|
||||
});
|
||||
*/
|
||||
// ORIGINAL CODE
|
||||
// $(container).bind('mousewheel',this,function(event, delta) {
|
||||
// event.data.rotate(delta);
|
||||
// return false;
|
||||
// });
|
||||
//
|
||||
}
|
||||
$(container).bind('mouseover click',this,function(event){
|
||||
$(container).unbind('mouseover click').bind('mouseover click',this,function(event){
|
||||
|
||||
clearInterval(event.data.autoRotateTimer); // Stop auto rotation if mouse over.
|
||||
var text = $(event.target).attr('alt');
|
||||
|
@ -206,22 +211,27 @@ jQuery.browser = browser;
|
|||
//$(options.titleBox).html( ($(event.target).attr('title') ));
|
||||
if ( options.bringToFront && event.type == 'click' )
|
||||
{
|
||||
$(options.titleBox).html( ($(event.target).attr('title') ));
|
||||
// METAMAPS CODE
|
||||
Metamaps.Create.newTopic.metacode = $(event.target).attr('data-id');
|
||||
// NOT METAMAPS CODE
|
||||
$(options.titleBox).html( ($(event.target).attr('title') ));
|
||||
// START METAMAPS CODE
|
||||
Metamaps.Create.newTopic.metacode = $(event.target).attr('data-id');
|
||||
// END METAMAPS CODE
|
||||
var idx = $(event.target).data('itemIndex');
|
||||
var frontIndex = event.data.frontIndex;
|
||||
//var diff = idx - frontIndex;
|
||||
var diff = (idx - frontIndex) % images.length;
|
||||
if (Math.abs(diff) > images.length / 2) {
|
||||
diff += (diff > 0 ? -images.length : images.length);
|
||||
}
|
||||
|
||||
var diff = (idx - frontIndex) % images.length;
|
||||
if (Math.abs(diff) > images.length / 2) {
|
||||
diff += (diff > 0 ? -images.length : images.length);
|
||||
}
|
||||
|
||||
event.data.rotate(-diff);
|
||||
}
|
||||
}
|
||||
});
|
||||
// START METAMAPS CODE - initialize newTopic.metacode
|
||||
var first = $(this.container).find('img').get(0)
|
||||
Metamaps.Create.newTopic.metacode = $(first).data('id')
|
||||
// END METAMAPS CODE
|
||||
|
||||
// If we have moved out of a carousel item (or the container itself),
|
||||
// restore the text of the front item in 1 second.
|
||||
$(container).bind('mouseout',this,function(event){
|
||||
|
@ -245,11 +255,6 @@ jQuery.browser = browser;
|
|||
this.showFrontText = function()
|
||||
{
|
||||
if ( items[this.frontIndex] === undefined ) { return; } // Images might not have loaded yet.
|
||||
// METAMAPS CODE
|
||||
Metamaps.Create.newTopic.metacode = $(items[this.frontIndex].image).attr('data-id');
|
||||
//$('img.cloudcarousel').css({"background":"none", "width":"","height":""});
|
||||
//$(items[this.frontIndex].image).css({"width":"45px","height":"45px"});
|
||||
// NOT METAMAPS CODE
|
||||
$(options.titleBox).html( $(items[this.frontIndex].image).attr('title'));
|
||||
$(options.altBox).html( $(items[this.frontIndex].image).attr('alt'));
|
||||
};
|
||||
|
@ -423,4 +428,4 @@ jQuery.browser = browser;
|
|||
return this;
|
||||
};
|
||||
|
||||
})(jQuery);
|
||||
})(jQuery);
|
||||
|
|
|
@ -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 );
|
||||
|
|
@ -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', {}]);
|
||||
};
|
|
@ -1,451 +0,0 @@
|
|||
/* global Metamaps, $ */
|
||||
|
||||
/*
|
||||
* Metamaps.Control.js.erb
|
||||
*
|
||||
* Dependencies:
|
||||
* - Metamaps.Active
|
||||
* - Metamaps.Control
|
||||
* - Metamaps.Filter
|
||||
* - Metamaps.GlobalUI
|
||||
* - Metamaps.JIT
|
||||
* - Metamaps.Mappings
|
||||
* - Metamaps.Metacodes
|
||||
* - Metamaps.Mouse
|
||||
* - Metamaps.Selected
|
||||
* - Metamaps.Settings
|
||||
* - Metamaps.Synapses
|
||||
* - Metamaps.Topics
|
||||
* - Metamaps.Visualize
|
||||
*/
|
||||
|
||||
Metamaps.Control = {
|
||||
init: function () {},
|
||||
selectNode: function (node, e) {
|
||||
var filtered = node.getData('alpha') === 0
|
||||
|
||||
if (filtered || Metamaps.Selected.Nodes.indexOf(node) != -1) return
|
||||
node.selected = true
|
||||
node.setData('dim', 30, 'current')
|
||||
Metamaps.Selected.Nodes.push(node)
|
||||
},
|
||||
deselectAllNodes: function () {
|
||||
var l = Metamaps.Selected.Nodes.length
|
||||
for (var i = l - 1; i >= 0; i -= 1) {
|
||||
var node = Metamaps.Selected.Nodes[i]
|
||||
Metamaps.Control.deselectNode(node)
|
||||
}
|
||||
Metamaps.Visualize.mGraph.plot()
|
||||
},
|
||||
deselectNode: function (node) {
|
||||
delete node.selected
|
||||
node.setData('dim', 25, 'current')
|
||||
|
||||
// remove the node
|
||||
Metamaps.Selected.Nodes.splice(
|
||||
Metamaps.Selected.Nodes.indexOf(node), 1)
|
||||
},
|
||||
deleteSelected: function () {
|
||||
if (!Metamaps.Active.Map) return
|
||||
|
||||
var n = Metamaps.Selected.Nodes.length
|
||||
var e = Metamaps.Selected.Edges.length
|
||||
var ntext = n == 1 ? '1 topic' : n + ' topics'
|
||||
var etext = e == 1 ? '1 synapse' : e + ' synapses'
|
||||
var text = 'You have ' + ntext + ' and ' + etext + ' selected. '
|
||||
|
||||
var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper)
|
||||
|
||||
if (!authorized) {
|
||||
Metamaps.GlobalUI.notifyUser('Cannot edit Public map.')
|
||||
return
|
||||
}
|
||||
|
||||
var r = confirm(text + 'Are you sure you want to permanently delete them all? This will remove them from all maps they appear on.')
|
||||
if (r == true) {
|
||||
Metamaps.Control.deleteSelectedEdges()
|
||||
Metamaps.Control.deleteSelectedNodes()
|
||||
}
|
||||
},
|
||||
deleteSelectedNodes: function () { // refers to deleting topics permanently
|
||||
if (!Metamaps.Active.Map) return
|
||||
|
||||
var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper)
|
||||
|
||||
if (!authorized) {
|
||||
Metamaps.GlobalUI.notifyUser('Cannot edit Public map.')
|
||||
return
|
||||
}
|
||||
|
||||
var l = Metamaps.Selected.Nodes.length
|
||||
for (var i = l - 1; i >= 0; i -= 1) {
|
||||
var node = Metamaps.Selected.Nodes[i]
|
||||
Metamaps.Control.deleteNode(node.id)
|
||||
}
|
||||
},
|
||||
deleteNode: function (nodeid) { // refers to deleting topics permanently
|
||||
if (!Metamaps.Active.Map) return
|
||||
|
||||
var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper)
|
||||
|
||||
if (!authorized) {
|
||||
Metamaps.GlobalUI.notifyUser('Cannot edit Public map.')
|
||||
return
|
||||
}
|
||||
|
||||
var node = Metamaps.Visualize.mGraph.graph.getNode(nodeid)
|
||||
var topic = node.getData('topic')
|
||||
|
||||
var permToDelete = Metamaps.Active.Mapper.id === topic.get('user_id') || Metamaps.Active.Mapper.get('admin')
|
||||
if (permToDelete) {
|
||||
var mappableid = topic.id
|
||||
var mapping = node.getData('mapping')
|
||||
topic.destroy()
|
||||
Metamaps.Mappings.remove(mapping)
|
||||
$(document).trigger(Metamaps.JIT.events.deleteTopic, [{
|
||||
mappableid: mappableid
|
||||
}])
|
||||
Metamaps.Control.hideNode(nodeid)
|
||||
} else {
|
||||
Metamaps.GlobalUI.notifyUser('Only topics you created can be deleted')
|
||||
}
|
||||
},
|
||||
removeSelectedNodes: function () { // refers to removing topics permanently from a map
|
||||
if (Metamaps.Active.Topic) {
|
||||
// hideNode will handle synapses as well
|
||||
var nodeids = _.map(Metamaps.Selected.Nodes, function(node) {
|
||||
return node.id
|
||||
})
|
||||
_.each(nodeids, function(nodeid) {
|
||||
if (Metamaps.Active.Topic.id !== nodeid) {
|
||||
Metamaps.Topics.remove(nodeid)
|
||||
Metamaps.Control.hideNode(nodeid)
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!Metamaps.Active.Map) return
|
||||
|
||||
var l = Metamaps.Selected.Nodes.length,
|
||||
i,
|
||||
node,
|
||||
authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper)
|
||||
|
||||
if (!authorized) {
|
||||
Metamaps.GlobalUI.notifyUser('Cannot edit Public map.')
|
||||
return
|
||||
}
|
||||
|
||||
for (i = l - 1; i >= 0; i -= 1) {
|
||||
node = Metamaps.Selected.Nodes[i]
|
||||
Metamaps.Control.removeNode(node.id)
|
||||
}
|
||||
},
|
||||
removeNode: function (nodeid) { // refers to removing topics permanently from a map
|
||||
if (!Metamaps.Active.Map) return
|
||||
|
||||
var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper)
|
||||
var node = Metamaps.Visualize.mGraph.graph.getNode(nodeid)
|
||||
|
||||
if (!authorized) {
|
||||
Metamaps.GlobalUI.notifyUser('Cannot edit Public map.')
|
||||
return
|
||||
}
|
||||
|
||||
var topic = node.getData('topic')
|
||||
var mappableid = topic.id
|
||||
var mapping = node.getData('mapping')
|
||||
mapping.destroy()
|
||||
Metamaps.Topics.remove(topic)
|
||||
$(document).trigger(Metamaps.JIT.events.removeTopic, [{
|
||||
mappableid: mappableid
|
||||
}])
|
||||
Metamaps.Control.hideNode(nodeid)
|
||||
},
|
||||
hideSelectedNodes: function () {
|
||||
var l = Metamaps.Selected.Nodes.length,
|
||||
i,
|
||||
node
|
||||
|
||||
for (i = l - 1; i >= 0; i -= 1) {
|
||||
node = Metamaps.Selected.Nodes[i]
|
||||
Metamaps.Control.hideNode(node.id)
|
||||
}
|
||||
},
|
||||
hideNode: function (nodeid) {
|
||||
var node = Metamaps.Visualize.mGraph.graph.getNode(nodeid)
|
||||
var graph = Metamaps.Visualize.mGraph
|
||||
|
||||
Metamaps.Control.deselectNode(node)
|
||||
|
||||
node.setData('alpha', 0, 'end')
|
||||
node.eachAdjacency(function (adj) {
|
||||
adj.setData('alpha', 0, 'end')
|
||||
})
|
||||
Metamaps.Visualize.mGraph.fx.animate({
|
||||
modes: ['node-property:alpha',
|
||||
'edge-property:alpha'
|
||||
],
|
||||
duration: 500
|
||||
})
|
||||
setTimeout(function () {
|
||||
if (nodeid == Metamaps.Visualize.mGraph.root) { // && Metamaps.Visualize.type === "RGraph"
|
||||
var newroot = _.find(graph.graph.nodes, function (n) { return n.id !== nodeid; })
|
||||
graph.root = newroot ? newroot.id : null
|
||||
}
|
||||
Metamaps.Visualize.mGraph.graph.removeNode(nodeid)
|
||||
}, 500)
|
||||
Metamaps.Filter.checkMetacodes()
|
||||
Metamaps.Filter.checkMappers()
|
||||
},
|
||||
selectEdge: function (edge) {
|
||||
var filtered = edge.getData('alpha') === 0; // don't select if the edge is filtered
|
||||
|
||||
if (filtered || Metamaps.Selected.Edges.indexOf(edge) != -1) return
|
||||
|
||||
var width = Metamaps.Mouse.edgeHoveringOver === edge ? 4 : 2
|
||||
edge.setDataset('current', {
|
||||
showDesc: true,
|
||||
lineWidth: width,
|
||||
color: Metamaps.Settings.colors.synapses.selected
|
||||
})
|
||||
Metamaps.Visualize.mGraph.plot()
|
||||
|
||||
Metamaps.Selected.Edges.push(edge)
|
||||
},
|
||||
deselectAllEdges: function () {
|
||||
var l = Metamaps.Selected.Edges.length
|
||||
for (var i = l - 1; i >= 0; i -= 1) {
|
||||
var edge = Metamaps.Selected.Edges[i]
|
||||
Metamaps.Control.deselectEdge(edge)
|
||||
}
|
||||
Metamaps.Visualize.mGraph.plot()
|
||||
},
|
||||
deselectEdge: function (edge) {
|
||||
edge.setData('showDesc', false, 'current')
|
||||
|
||||
edge.setDataset('current', {
|
||||
lineWidth: 2,
|
||||
color: Metamaps.Settings.colors.synapses.normal
|
||||
})
|
||||
|
||||
if (Metamaps.Mouse.edgeHoveringOver == edge) {
|
||||
edge.setDataset('current', {
|
||||
showDesc: true,
|
||||
lineWidth: 4
|
||||
})
|
||||
}
|
||||
|
||||
Metamaps.Visualize.mGraph.plot()
|
||||
|
||||
// remove the edge
|
||||
Metamaps.Selected.Edges.splice(
|
||||
Metamaps.Selected.Edges.indexOf(edge), 1)
|
||||
},
|
||||
deleteSelectedEdges: function () { // refers to deleting topics permanently
|
||||
var edge,
|
||||
l = Metamaps.Selected.Edges.length
|
||||
|
||||
if (!Metamaps.Active.Map) return
|
||||
|
||||
var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper)
|
||||
|
||||
if (!authorized) {
|
||||
Metamaps.GlobalUI.notifyUser('Cannot edit Public map.')
|
||||
return
|
||||
}
|
||||
|
||||
for (var i = l - 1; i >= 0; i -= 1) {
|
||||
edge = Metamaps.Selected.Edges[i]
|
||||
Metamaps.Control.deleteEdge(edge)
|
||||
}
|
||||
},
|
||||
deleteEdge: function (edge) {
|
||||
if (!Metamaps.Active.Map) return
|
||||
|
||||
var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper)
|
||||
|
||||
if (!authorized) {
|
||||
Metamaps.GlobalUI.notifyUser('Cannot edit Public map.')
|
||||
return
|
||||
}
|
||||
|
||||
var index = edge.getData('displayIndex') ? edge.getData('displayIndex') : 0
|
||||
|
||||
var synapse = edge.getData('synapses')[index]
|
||||
var mapping = edge.getData('mappings')[index]
|
||||
|
||||
var permToDelete = Metamaps.Active.Mapper.id === synapse.get('user_id') || Metamaps.Active.Mapper.get('admin')
|
||||
if (permToDelete) {
|
||||
if (edge.getData('synapses').length - 1 === 0) {
|
||||
Metamaps.Control.hideEdge(edge)
|
||||
}
|
||||
var mappableid = synapse.id
|
||||
synapse.destroy()
|
||||
|
||||
// the server will destroy the mapping, we just need to remove it here
|
||||
Metamaps.Mappings.remove(mapping)
|
||||
edge.getData('mappings').splice(index, 1)
|
||||
edge.getData('synapses').splice(index, 1)
|
||||
if (edge.getData('displayIndex')) {
|
||||
delete edge.data.$displayIndex
|
||||
}
|
||||
$(document).trigger(Metamaps.JIT.events.deleteSynapse, [{
|
||||
mappableid: mappableid
|
||||
}])
|
||||
} else {
|
||||
Metamaps.GlobalUI.notifyUser('Only synapses you created can be deleted')
|
||||
}
|
||||
},
|
||||
removeSelectedEdges: function () {
|
||||
// Topic view is handled by removeSelectedNodes
|
||||
if (!Metamaps.Active.Map) return
|
||||
|
||||
var l = Metamaps.Selected.Edges.length,
|
||||
i,
|
||||
edge
|
||||
|
||||
var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper)
|
||||
|
||||
if (!authorized) {
|
||||
Metamaps.GlobalUI.notifyUser('Cannot edit Public map.')
|
||||
return
|
||||
}
|
||||
|
||||
for (i = l - 1; i >= 0; i -= 1) {
|
||||
edge = Metamaps.Selected.Edges[i]
|
||||
Metamaps.Control.removeEdge(edge)
|
||||
}
|
||||
Metamaps.Selected.Edges = [ ]
|
||||
},
|
||||
removeEdge: function (edge) {
|
||||
if (!Metamaps.Active.Map) return
|
||||
|
||||
var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper)
|
||||
|
||||
if (!authorized) {
|
||||
Metamaps.GlobalUI.notifyUser('Cannot edit Public map.')
|
||||
return
|
||||
}
|
||||
|
||||
if (edge.getData('mappings').length - 1 === 0) {
|
||||
Metamaps.Control.hideEdge(edge)
|
||||
}
|
||||
|
||||
var index = edge.getData('displayIndex') ? edge.getData('displayIndex') : 0
|
||||
|
||||
var synapse = edge.getData('synapses')[index]
|
||||
var mapping = edge.getData('mappings')[index]
|
||||
var mappableid = synapse.id
|
||||
mapping.destroy()
|
||||
|
||||
Metamaps.Synapses.remove(synapse)
|
||||
|
||||
edge.getData('mappings').splice(index, 1)
|
||||
edge.getData('synapses').splice(index, 1)
|
||||
if (edge.getData('displayIndex')) {
|
||||
delete edge.data.$displayIndex
|
||||
}
|
||||
$(document).trigger(Metamaps.JIT.events.removeSynapse, [{
|
||||
mappableid: mappableid
|
||||
}])
|
||||
},
|
||||
hideSelectedEdges: function () {
|
||||
var edge,
|
||||
l = Metamaps.Selected.Edges.length,
|
||||
i
|
||||
for (i = l - 1; i >= 0; i -= 1) {
|
||||
edge = Metamaps.Selected.Edges[i]
|
||||
Metamaps.Control.hideEdge(edge)
|
||||
}
|
||||
Metamaps.Selected.Edges = [ ]
|
||||
},
|
||||
hideEdge: function (edge) {
|
||||
var from = edge.nodeFrom.id
|
||||
var to = edge.nodeTo.id
|
||||
edge.setData('alpha', 0, 'end')
|
||||
Metamaps.Control.deselectEdge(edge)
|
||||
Metamaps.Visualize.mGraph.fx.animate({
|
||||
modes: ['edge-property:alpha'],
|
||||
duration: 500
|
||||
})
|
||||
setTimeout(function () {
|
||||
Metamaps.Visualize.mGraph.graph.removeAdjacence(from, to)
|
||||
}, 500)
|
||||
Metamaps.Filter.checkSynapses()
|
||||
Metamaps.Filter.checkMappers()
|
||||
},
|
||||
updateSelectedPermissions: function (permission) {
|
||||
var edge, synapse, node, topic
|
||||
|
||||
Metamaps.GlobalUI.notifyUser('Working...')
|
||||
|
||||
// variables to keep track of how many nodes and synapses you had the ability to change the permission of
|
||||
var nCount = 0,
|
||||
sCount = 0
|
||||
|
||||
// change the permission of the selected synapses, if logged in user is the original creator
|
||||
var l = Metamaps.Selected.Edges.length
|
||||
for (var i = l - 1; i >= 0; i -= 1) {
|
||||
edge = Metamaps.Selected.Edges[i]
|
||||
synapse = edge.getData('synapses')[0]
|
||||
|
||||
if (synapse.authorizePermissionChange(Metamaps.Active.Mapper)) {
|
||||
synapse.save({
|
||||
permission: permission
|
||||
})
|
||||
sCount++
|
||||
}
|
||||
}
|
||||
|
||||
// change the permission of the selected topics, if logged in user is the original creator
|
||||
var l = Metamaps.Selected.Nodes.length
|
||||
for (var i = l - 1; i >= 0; i -= 1) {
|
||||
node = Metamaps.Selected.Nodes[i]
|
||||
topic = node.getData('topic')
|
||||
|
||||
if (topic.authorizePermissionChange(Metamaps.Active.Mapper)) {
|
||||
topic.save({
|
||||
permission: permission
|
||||
})
|
||||
nCount++
|
||||
}
|
||||
}
|
||||
|
||||
var nString = nCount == 1 ? (nCount.toString() + ' topic and ') : (nCount.toString() + ' topics and ')
|
||||
var sString = sCount == 1 ? (sCount.toString() + ' synapse') : (sCount.toString() + ' synapses')
|
||||
|
||||
var message = nString + sString + ' you created updated to ' + permission
|
||||
Metamaps.GlobalUI.notifyUser(message)
|
||||
},
|
||||
updateSelectedMetacodes: function (metacode_id) {
|
||||
var node, topic
|
||||
|
||||
Metamaps.GlobalUI.notifyUser('Working...')
|
||||
|
||||
var metacode = Metamaps.Metacodes.get(metacode_id)
|
||||
|
||||
// variables to keep track of how many nodes and synapses you had the ability to change the permission of
|
||||
var nCount = 0
|
||||
|
||||
// change the permission of the selected topics, if logged in user is the original creator
|
||||
var l = Metamaps.Selected.Nodes.length
|
||||
for (var i = l - 1; i >= 0; i -= 1) {
|
||||
node = Metamaps.Selected.Nodes[i]
|
||||
topic = node.getData('topic')
|
||||
|
||||
if (topic.authorizeToEdit(Metamaps.Active.Mapper)) {
|
||||
topic.save({
|
||||
'metacode_id': metacode_id
|
||||
})
|
||||
nCount++
|
||||
}
|
||||
}
|
||||
|
||||
var nString = nCount == 1 ? (nCount.toString() + ' topic') : (nCount.toString() + ' topics')
|
||||
|
||||
var message = nString + ' you can edit updated to ' + metacode.get('name')
|
||||
Metamaps.GlobalUI.notifyUser(message)
|
||||
Metamaps.Visualize.mGraph.plot()
|
||||
},
|
||||
}; // end Metamaps.Control
|
|
@ -1,13 +0,0 @@
|
|||
/*
|
||||
* Metamaps.Debug.js.erb
|
||||
*
|
||||
* Dependencies: none!
|
||||
*/
|
||||
|
||||
Metamaps.Debug = function () {
|
||||
console.debug(Metamaps)
|
||||
console.debug(`Metamaps Version: ${Metamaps.VERSION}`)
|
||||
}
|
||||
Metamaps.debug = function () {
|
||||
Metamaps.Debug()
|
||||
}
|
24
app/assets/javascripts/src/Metamaps.Erb.js.erb
Normal file
|
@ -0,0 +1,24 @@
|
|||
/* global Metamaps */
|
||||
|
||||
/*
|
||||
* Metamaps.Erb.js.erb
|
||||
*/
|
||||
|
||||
/* erb variables from rails */
|
||||
window.Metamaps = window.Metamaps || {}
|
||||
Metamaps.Erb = {}
|
||||
Metamaps.Erb['REALTIME_SERVER'] = '<%= ENV['REALTIME_SERVER'] %>'
|
||||
Metamaps.Erb['RAILS_ENV'] = '<%= ENV['RAILS_ENV'] %>'
|
||||
Metamaps.Erb['junto_spinner_darkgrey.gif'] = '<%= asset_path('junto_spinner_darkgrey.gif') %>'
|
||||
Metamaps.Erb['user.png'] = '<%= asset_path('user.png') %>'
|
||||
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 %>
|
||||
Metamaps.VERSION = '<%= METAMAPS_VERSION %>'
|
||||
Metamaps.BUILD = '<%= METAMAPS_BUILD %>'
|
||||
Metamaps.LAST_UPDATED = '<%= METAMAPS_LAST_UPDATED %>'
|
|
@ -1,717 +0,0 @@
|
|||
var Metamaps = window.Metamaps || {}; // this variable declaration defines a Javascript object that will contain all the variables and functions used by us, broken down into 'sub-modules' that look something like this
|
||||
/*
|
||||
|
||||
* unless you are on a page with the Javascript InfoVis Toolkit (Topic or Map) the only section in the metamaps
|
||||
* object will be these
|
||||
GlobalUI
|
||||
Active
|
||||
Maps
|
||||
Mappers
|
||||
Backbone
|
||||
|
||||
* all these get added when you are on a page with the Javascript Infovis Toolkit
|
||||
Settings
|
||||
Touch
|
||||
Mouse
|
||||
Selected
|
||||
Metacodes
|
||||
Topics
|
||||
Synapses
|
||||
Mappings
|
||||
Create
|
||||
TopicCard
|
||||
SynapseCard
|
||||
Visualize
|
||||
Util
|
||||
Realtime
|
||||
Control
|
||||
Filter
|
||||
Listeners
|
||||
Organize
|
||||
Map
|
||||
Mapper
|
||||
Topic
|
||||
Synapse
|
||||
JIT
|
||||
*/
|
||||
|
||||
Metamaps.Active = {
|
||||
Map: null,
|
||||
Topic: null,
|
||||
Mapper: null
|
||||
};
|
||||
Metamaps.Maps = {};
|
||||
|
||||
$(document).ready(function () {
|
||||
// initialize all the modules
|
||||
for (var prop in Metamaps) {
|
||||
// this runs the init function within each sub-object on the Metamaps one
|
||||
if (Metamaps.hasOwnProperty(prop) &&
|
||||
Metamaps[prop] != null &&
|
||||
Metamaps[prop].hasOwnProperty('init') &&
|
||||
typeof (Metamaps[prop].init) == 'function'
|
||||
) {
|
||||
Metamaps[prop].init()
|
||||
}
|
||||
}
|
||||
// load whichever page you are on
|
||||
if (Metamaps.currentSection === "explore") {
|
||||
var capitalize = Metamaps.currentPage.charAt(0).toUpperCase() + Metamaps.currentPage.slice(1)
|
||||
|
||||
Metamaps.Views.exploreMaps.setCollection( Metamaps.Maps[capitalize] )
|
||||
if (Metamaps.currentPage === "mapper") {
|
||||
Metamaps.Views.exploreMaps.fetchUserThenRender()
|
||||
}
|
||||
else {
|
||||
Metamaps.Views.exploreMaps.render()
|
||||
}
|
||||
Metamaps.GlobalUI.showDiv('#explore')
|
||||
}
|
||||
else if (Metamaps.currentSection === "" && Metamaps.Active.Mapper) {
|
||||
Metamaps.Views.exploreMaps.setCollection(Metamaps.Maps.Active)
|
||||
Metamaps.Views.exploreMaps.render()
|
||||
Metamaps.GlobalUI.showDiv('#explore')
|
||||
}
|
||||
else if (Metamaps.Active.Map || Metamaps.Active.Topic) {
|
||||
Metamaps.Loading.show()
|
||||
Metamaps.JIT.prepareVizData()
|
||||
Metamaps.GlobalUI.showDiv('#infovis')
|
||||
}
|
||||
});
|
||||
|
||||
Metamaps.GlobalUI = {
|
||||
notifyTimeout: null,
|
||||
lightbox: null,
|
||||
init: function () {
|
||||
var self = Metamaps.GlobalUI;
|
||||
|
||||
self.Search.init();
|
||||
self.CreateMap.init();
|
||||
self.Account.init();
|
||||
|
||||
if ($('#toast').html().trim()) self.notifyUser($('#toast').html())
|
||||
|
||||
//bind lightbox clicks
|
||||
$('.openLightbox').click(function (event) {
|
||||
self.openLightbox($(this).attr('data-open'));
|
||||
event.preventDefault();
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#lightbox_screen, #lightbox_close').click(self.closeLightbox);
|
||||
|
||||
// initialize global backbone models and collections
|
||||
if (Metamaps.Active.Mapper) Metamaps.Active.Mapper = new Metamaps.Backbone.Mapper(Metamaps.Active.Mapper);
|
||||
|
||||
var myCollection = Metamaps.Maps.Mine ? Metamaps.Maps.Mine : [];
|
||||
var sharedCollection = Metamaps.Maps.Shared ? Metamaps.Maps.Shared : [];
|
||||
var starredCollection = Metamaps.Maps.Starred ? Metamaps.Maps.Starred : [];
|
||||
var mapperCollection = [];
|
||||
var mapperOptionsObj = {id: 'mapper', sortBy: 'updated_at' };
|
||||
if (Metamaps.Maps.Mapper) {
|
||||
mapperCollection = Metamaps.Maps.Mapper.models;
|
||||
mapperOptionsObj.mapperId = Metamaps.Maps.Mapper.id;
|
||||
}
|
||||
var featuredCollection = Metamaps.Maps.Featured ? Metamaps.Maps.Featured : [];
|
||||
var activeCollection = Metamaps.Maps.Active ? Metamaps.Maps.Active : [];
|
||||
Metamaps.Maps.Mine = new Metamaps.Backbone.MapsCollection(myCollection, {id: 'mine', sortBy: 'updated_at' });
|
||||
Metamaps.Maps.Shared = new Metamaps.Backbone.MapsCollection(sharedCollection, {id: 'shared', sortBy: 'updated_at' });
|
||||
Metamaps.Maps.Starred = new Metamaps.Backbone.MapsCollection(starredCollection, {id: 'starred', sortBy: 'updated_at' });
|
||||
// 'Mapper' refers to another mapper
|
||||
Metamaps.Maps.Mapper = new Metamaps.Backbone.MapsCollection(mapperCollection, mapperOptionsObj);
|
||||
Metamaps.Maps.Featured = new Metamaps.Backbone.MapsCollection(featuredCollection, {id: 'featured', sortBy: 'updated_at' });
|
||||
Metamaps.Maps.Active = new Metamaps.Backbone.MapsCollection(activeCollection, {id: 'active', sortBy: 'updated_at' });
|
||||
},
|
||||
showDiv: function (selector) {
|
||||
$(selector).show()
|
||||
$(selector).animate({
|
||||
opacity: 1
|
||||
}, 200, 'easeOutCubic')
|
||||
},
|
||||
hideDiv: function (selector) {
|
||||
$(selector).animate({
|
||||
opacity: 0
|
||||
}, 200, 'easeInCubic', function () { $(this).hide() })
|
||||
},
|
||||
openLightbox: function (which) {
|
||||
var self = Metamaps.GlobalUI;
|
||||
|
||||
$('.lightboxContent').hide();
|
||||
$('#' + which).show();
|
||||
|
||||
self.lightbox = which;
|
||||
|
||||
$('#lightbox_overlay').show();
|
||||
|
||||
var heightOfContent = '-' + ($('#lightbox_main').height() / 2) + 'px';
|
||||
// animate the content in from the bottom
|
||||
$('#lightbox_main').animate({
|
||||
'top': '50%',
|
||||
'margin-top': heightOfContent
|
||||
}, 200, 'easeOutCubic');
|
||||
|
||||
// fade the black overlay in
|
||||
$('#lightbox_screen').animate({
|
||||
'opacity': '0.42'
|
||||
}, 200);
|
||||
|
||||
if (which == "switchMetacodes") {
|
||||
Metamaps.Create.isSwitchingSet = true;
|
||||
}
|
||||
},
|
||||
|
||||
closeLightbox: function (event) {
|
||||
var self = Metamaps.GlobalUI;
|
||||
|
||||
if (event) event.preventDefault();
|
||||
|
||||
// animate the lightbox content offscreen
|
||||
$('#lightbox_main').animate({
|
||||
'top': '100%',
|
||||
'margin-top': '0'
|
||||
}, 200, 'easeInCubic');
|
||||
|
||||
// fade the black overlay out
|
||||
$('#lightbox_screen').animate({
|
||||
'opacity': '0.0'
|
||||
}, 200, function () {
|
||||
$('#lightbox_overlay').hide();
|
||||
});
|
||||
|
||||
if (self.lightbox === 'forkmap') Metamaps.GlobalUI.CreateMap.reset('fork_map');
|
||||
if (self.lightbox === 'newmap') Metamaps.GlobalUI.CreateMap.reset('new_map');
|
||||
if (Metamaps.Create && Metamaps.Create.isSwitchingSet) {
|
||||
Metamaps.Create.cancelMetacodeSetSwitch();
|
||||
}
|
||||
self.lightbox = null;
|
||||
},
|
||||
notifyUser: function (message, leaveOpen) {
|
||||
var self = Metamaps.GlobalUI;
|
||||
|
||||
$('#toast').html(message)
|
||||
self.showDiv('#toast')
|
||||
clearTimeout(self.notifyTimeOut);
|
||||
if (!leaveOpen) {
|
||||
self.notifyTimeOut = setTimeout(function () {
|
||||
self.hideDiv('#toast')
|
||||
}, 8000);
|
||||
}
|
||||
},
|
||||
clearNotify: function() {
|
||||
var self = Metamaps.GlobalUI;
|
||||
|
||||
clearTimeout(self.notifyTimeOut);
|
||||
self.hideDiv('#toast')
|
||||
},
|
||||
shareInvite: function(inviteLink) {
|
||||
window.prompt("To copy the invite link, press: Ctrl+C, Enter", inviteLink);
|
||||
}
|
||||
};
|
||||
|
||||
Metamaps.GlobalUI.CreateMap = {
|
||||
newMap: null,
|
||||
emptyMapForm: "",
|
||||
emptyForkMapForm: "",
|
||||
topicsToMap: [],
|
||||
synapsesToMap: [],
|
||||
init: function () {
|
||||
var self = Metamaps.GlobalUI.CreateMap;
|
||||
|
||||
self.newMap = new Metamaps.Backbone.Map({ permission: 'commons' });
|
||||
|
||||
self.bindFormEvents();
|
||||
|
||||
self.emptyMapForm = $('#new_map').html();
|
||||
|
||||
},
|
||||
bindFormEvents: function () {
|
||||
var self = Metamaps.GlobalUI.CreateMap;
|
||||
|
||||
$('.new_map input, .new_map div').unbind('keypress').bind('keypress', function(event) {
|
||||
if (event.keyCode === 13) self.submit()
|
||||
})
|
||||
|
||||
$('.new_map button.cancel').unbind().bind('click', function (event) {
|
||||
event.preventDefault();
|
||||
Metamaps.GlobalUI.closeLightbox();
|
||||
});
|
||||
$('.new_map button.submitMap').unbind().bind('click', self.submit);
|
||||
|
||||
// bind permission changer events on the createMap form
|
||||
$('.permIcon').unbind().bind('click', self.switchPermission);
|
||||
},
|
||||
closeSuccess: function () {
|
||||
$('#mapCreatedSuccess').fadeOut(300, function(){
|
||||
$(this).remove();
|
||||
});
|
||||
},
|
||||
generateSuccessMessage: function (id) {
|
||||
var stringStart = "<div id='mapCreatedSuccess'><h6>SUCCESS!</h6>Your map has been created. Do you want to: <a id='mapGo' href='/maps/";
|
||||
stringStart += id;
|
||||
stringStart += "' onclick='Metamaps.GlobalUI.CreateMap.closeSuccess();'>Go to your new map</a>";
|
||||
stringStart += "<span>OR</span><a id='mapStay' href='#' onclick='Metamaps.GlobalUI.CreateMap.closeSuccess(); return false;'>Stay on this ";
|
||||
var page = Metamaps.Active.Map ? 'map' : 'page';
|
||||
var stringEnd = "</a></div>";
|
||||
return stringStart + page + stringEnd;
|
||||
},
|
||||
switchPermission: function () {
|
||||
var self = Metamaps.GlobalUI.CreateMap;
|
||||
|
||||
self.newMap.set('permission', $(this).attr('data-permission'));
|
||||
$(this).siblings('.permIcon').find('.mapPermIcon').removeClass('selected');
|
||||
$(this).find('.mapPermIcon').addClass('selected');
|
||||
|
||||
var permText = $(this).find('.tip').html();
|
||||
$(this).parents('.new_map').find('.permText').html(permText);
|
||||
},
|
||||
submit: function (event) {
|
||||
if (event) event.preventDefault();
|
||||
|
||||
var self = Metamaps.GlobalUI.CreateMap;
|
||||
|
||||
if (Metamaps.GlobalUI.lightbox === 'forkmap') {
|
||||
self.newMap.set('topicsToMap', self.topicsToMap);
|
||||
self.newMap.set('synapsesToMap', self.synapsesToMap);
|
||||
}
|
||||
|
||||
var formId = Metamaps.GlobalUI.lightbox === 'forkmap' ? '#fork_map' : '#new_map';
|
||||
var $form = $(formId);
|
||||
|
||||
self.newMap.set('name', $form.find('#map_name').val());
|
||||
self.newMap.set('desc', $form.find('#map_desc').val());
|
||||
|
||||
if (self.newMap.get('name').length===0){
|
||||
self.throwMapNameError();
|
||||
return;
|
||||
}
|
||||
|
||||
self.newMap.save(null, {
|
||||
success: self.success
|
||||
// TODO add error message
|
||||
});
|
||||
|
||||
Metamaps.GlobalUI.closeLightbox();
|
||||
Metamaps.GlobalUI.notifyUser('Working...');
|
||||
},
|
||||
throwMapNameError: function () {
|
||||
var self = Metamaps.GlobalUI.CreateMap;
|
||||
|
||||
var formId = Metamaps.GlobalUI.lightbox === 'forkmap' ? '#fork_map' : '#new_map';
|
||||
var $form = $(formId);
|
||||
|
||||
var message = $("<div class='feedback_message'>Please enter a map name...</div>");
|
||||
|
||||
$form.find('#map_name').after(message);
|
||||
setTimeout(function(){
|
||||
message.fadeOut('fast', function(){
|
||||
message.remove();
|
||||
});
|
||||
}, 5000);
|
||||
},
|
||||
success: function (model) {
|
||||
var self = Metamaps.GlobalUI.CreateMap;
|
||||
|
||||
//push the new map onto the collection of 'my maps'
|
||||
Metamaps.Maps.Mine.add(model);
|
||||
|
||||
var formId = Metamaps.GlobalUI.lightbox === 'forkmap' ? '#fork_map' : '#new_map';
|
||||
var form = $(formId);
|
||||
|
||||
Metamaps.GlobalUI.clearNotify();
|
||||
$('#wrapper').append(self.generateSuccessMessage(model.id));
|
||||
|
||||
},
|
||||
reset: function (id) {
|
||||
var self = Metamaps.GlobalUI.CreateMap;
|
||||
|
||||
var form = $('#' + id);
|
||||
|
||||
if (id === "fork_map") {
|
||||
self.topicsToMap = [];
|
||||
self.synapsesToMap = [];
|
||||
form.html(self.emptyForkMapForm);
|
||||
}
|
||||
else {
|
||||
form.html(self.emptyMapForm);
|
||||
}
|
||||
|
||||
self.bindFormEvents();
|
||||
self.newMap = new Metamaps.Backbone.Map({ permission: 'commons' });
|
||||
|
||||
return false;
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Metamaps.GlobalUI.Account = {
|
||||
isOpen: false,
|
||||
changing: false,
|
||||
init: function () {
|
||||
var self = Metamaps.GlobalUI.Account;
|
||||
|
||||
$('.sidebarAccountIcon').click(self.toggleBox);
|
||||
$('.sidebarAccountBox').click(function(event){
|
||||
event.stopPropagation();
|
||||
});
|
||||
$('body').click(self.close);
|
||||
},
|
||||
toggleBox: function (event) {
|
||||
var self = Metamaps.GlobalUI.Account;
|
||||
|
||||
if (self.isOpen) self.close();
|
||||
else self.open();
|
||||
|
||||
event.stopPropagation();
|
||||
},
|
||||
open: function () {
|
||||
var self = Metamaps.GlobalUI.Account;
|
||||
|
||||
Metamaps.Filter.close();
|
||||
$('.sidebarAccountIcon .tooltipsUnder').addClass('hide');
|
||||
|
||||
|
||||
if (!self.isOpen && !self.changing) {
|
||||
self.changing = true;
|
||||
$('.sidebarAccountBox').fadeIn(200, function () {
|
||||
self.changing = false;
|
||||
self.isOpen = true;
|
||||
$('.sidebarAccountBox #user_email').focus();
|
||||
});
|
||||
}
|
||||
},
|
||||
close: function () {
|
||||
var self = Metamaps.GlobalUI.Account;
|
||||
|
||||
$('.sidebarAccountIcon .tooltipsUnder').removeClass('hide');
|
||||
if (!self.changing) {
|
||||
self.changing = true;
|
||||
$('.sidebarAccountBox #user_email').blur();
|
||||
$('.sidebarAccountBox').fadeOut(200, function () {
|
||||
self.changing = false;
|
||||
self.isOpen = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
Metamaps.GlobalUI.Search = {
|
||||
locked: false,
|
||||
isOpen: false,
|
||||
limitTopicsToMe: false,
|
||||
limitMapsToMe: false,
|
||||
timeOut: null,
|
||||
changing: false,
|
||||
optionsInitialized: false,
|
||||
init: function () {
|
||||
var self = Metamaps.GlobalUI.Search;
|
||||
|
||||
var loader = new CanvasLoader('searchLoading');
|
||||
loader.setColor('#4fb5c0'); // default is '#000000'
|
||||
loader.setDiameter(24); // default is 40
|
||||
loader.setDensity(41); // default is 40
|
||||
loader.setRange(0.9); // default is 1.3
|
||||
loader.show(); // Hidden by default
|
||||
|
||||
// bind the hover events
|
||||
$(".sidebarSearch").hover(function () {
|
||||
self.open()
|
||||
}, function () {
|
||||
self.close(800, false)
|
||||
});
|
||||
|
||||
$('.sidebarSearchIcon').click(function (e) {
|
||||
$('.sidebarSearchField').focus();
|
||||
});
|
||||
$('.sidebarSearch').click(function (e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
$('body').click(function (e) {
|
||||
self.close(0, false);
|
||||
});
|
||||
|
||||
// open if the search is closed and user hits ctrl+/
|
||||
// close if they hit ESC
|
||||
$('body').bind('keyup', function (e) {
|
||||
switch (e.which) {
|
||||
case 191:
|
||||
if ((e.ctrlKey && !self.isOpen) || (e.ctrlKey && self.locked)) {
|
||||
self.open(true); // true for focus
|
||||
}
|
||||
break;
|
||||
case 27:
|
||||
if (self.isOpen) {
|
||||
self.close(0, true);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break; //console.log(e.which);
|
||||
}
|
||||
});
|
||||
|
||||
self.startTypeahead();
|
||||
},
|
||||
lock: function() {
|
||||
var self = Metamaps.GlobalUI.Search;
|
||||
self.locked = true;
|
||||
},
|
||||
unlock: function() {
|
||||
var self = Metamaps.GlobalUI.Search;
|
||||
self.locked = false;
|
||||
},
|
||||
open: function (focus) {
|
||||
var self = Metamaps.GlobalUI.Search;
|
||||
|
||||
clearTimeout(self.timeOut);
|
||||
if (!self.isOpen && !self.changing && !self.locked) {
|
||||
self.changing = true;
|
||||
$('.sidebarSearch .twitter-typeahead, .sidebarSearch .tt-hint, .sidebarSearchField').animate({
|
||||
width: '400px'
|
||||
}, 300, function () {
|
||||
if (focus) $('.sidebarSearchField').focus();
|
||||
$('.sidebarSearchField, .sidebarSearch .tt-hint').css({
|
||||
padding: '7px 10px 3px 10px',
|
||||
width: '380px'
|
||||
});
|
||||
self.changing = false;
|
||||
self.isOpen = true;
|
||||
});
|
||||
}
|
||||
},
|
||||
close: function (closeAfter, bypass) {
|
||||
// for now
|
||||
return
|
||||
|
||||
var self = Metamaps.GlobalUI.Search;
|
||||
|
||||
self.timeOut = setTimeout(function () {
|
||||
if (!self.locked && !self.changing && self.isOpen && (bypass || $('.sidebarSearchField.tt-input').val() == '')) {
|
||||
self.changing = true;
|
||||
$('.sidebarSearchField, .sidebarSearch .tt-hint').css({
|
||||
padding: '7px 0 3px 0',
|
||||
width: '400px'
|
||||
});
|
||||
$('.sidebarSearch .twitter-typeahead, .sidebarSearch .tt-hint, .sidebarSearchField').animate({
|
||||
width: '0'
|
||||
}, 300, function () {
|
||||
$('.sidebarSearchField').typeahead('val', '');
|
||||
$('.sidebarSearchField').blur();
|
||||
self.changing = false;
|
||||
self.isOpen = false;
|
||||
});
|
||||
}
|
||||
}, closeAfter);
|
||||
},
|
||||
startTypeahead: function () {
|
||||
var self = Metamaps.GlobalUI.Search;
|
||||
|
||||
var mapheader = Metamaps.Active.Mapper ? '<div class="searchMapsHeader searchHeader"><h3 class="search-heading">Maps</h3><input type="checkbox" class="limitToMe" id="limitMapsToMe"></input><label for="limitMapsToMe" class="limitToMeLabel">added by me</label><div class="minimizeResults minimizeMapResults"></div><div class="clearfloat"></div></div>' : '<div class="searchMapsHeader searchHeader"><h3 class="search-heading">Maps</h3><div class="minimizeResults minimizeMapResults"></div><div class="clearfloat"></div></div>';
|
||||
var topicheader = Metamaps.Active.Mapper ? '<div class="searchTopicsHeader searchHeader"><h3 class="search-heading">Topics</h3><input type="checkbox" class="limitToMe" id="limitTopicsToMe"></input><label for="limitTopicsToMe" class="limitToMeLabel">added by me</label><div class="minimizeResults minimizeTopicResults"></div><div class="clearfloat"></div></div>' : '<div class="searchTopicsHeader searchHeader"><h3 class="search-heading">Topics</h3><div class="minimizeResults minimizeTopicResults"></div><div class="clearfloat"></div></div>';
|
||||
var mapperheader = '<div class="searchMappersHeader searchHeader"><h3 class="search-heading">Mappers</h3><div class="minimizeResults minimizeMapperResults"></div><div class="clearfloat"></div></div>';
|
||||
|
||||
var topics = {
|
||||
name: 'topics',
|
||||
limit: 9999,
|
||||
|
||||
display: function(s) { return s.label; },
|
||||
templates: {
|
||||
notFound: function(s) {
|
||||
return Hogan.compile(topicheader + $('#topicSearchTemplate').html()).render({
|
||||
value: "No results",
|
||||
label: "No results",
|
||||
typeImageURL: Metamaps.Erb['icons/wildcard.png'],
|
||||
rtype: "noresult"
|
||||
});
|
||||
},
|
||||
header: topicheader,
|
||||
suggestion: function(s) {
|
||||
return Hogan.compile($('#topicSearchTemplate').html()).render(s);
|
||||
},
|
||||
},
|
||||
source: new Bloodhound({
|
||||
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
|
||||
queryTokenizer: Bloodhound.tokenizers.whitespace,
|
||||
remote: {
|
||||
url: '/search/topics',
|
||||
prepare: function(query, settings) {
|
||||
settings.url += '?term=' + query;
|
||||
if (Metamaps.Active.Mapper && self.limitTopicsToMe) {
|
||||
settings.url += "&user=" + Metamaps.Active.Mapper.id.toString();
|
||||
}
|
||||
return settings;
|
||||
},
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
var maps = {
|
||||
name: 'maps',
|
||||
limit: 9999,
|
||||
display: function(s) { return s.label; },
|
||||
templates: {
|
||||
notFound: function(s) {
|
||||
return Hogan.compile(mapheader + $('#mapSearchTemplate').html()).render({
|
||||
value: "No results",
|
||||
label: "No results",
|
||||
rtype: "noresult"
|
||||
});
|
||||
},
|
||||
header: mapheader,
|
||||
suggestion: function(s) {
|
||||
return Hogan.compile($('#mapSearchTemplate').html()).render(s);
|
||||
},
|
||||
},
|
||||
source: new Bloodhound({
|
||||
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
|
||||
queryTokenizer: Bloodhound.tokenizers.whitespace,
|
||||
remote: {
|
||||
url: '/search/maps',
|
||||
prepare: function(query, settings) {
|
||||
settings.url += '?term=' + query;
|
||||
if (Metamaps.Active.Mapper && self.limitMapsToMe) {
|
||||
settings.url += "&user=" + Metamaps.Active.Mapper.id.toString();
|
||||
}
|
||||
return settings;
|
||||
},
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
var mappers = {
|
||||
name: 'mappers',
|
||||
limit: 9999,
|
||||
display: function(s) { return s.label; },
|
||||
templates: {
|
||||
notFound: function(s) {
|
||||
return Hogan.compile(mapperheader + $('#mapperSearchTemplate').html()).render({
|
||||
value: "No results",
|
||||
label: "No results",
|
||||
rtype: "noresult",
|
||||
profile: Metamaps.Erb['user.png']
|
||||
});
|
||||
},
|
||||
header: mapperheader,
|
||||
suggestion: function(s) {
|
||||
return Hogan.compile($('#mapperSearchTemplate').html()).render(s);
|
||||
},
|
||||
},
|
||||
source: new Bloodhound({
|
||||
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
|
||||
queryTokenizer: Bloodhound.tokenizers.whitespace,
|
||||
remote: {
|
||||
url: '/search/mappers?term=%QUERY',
|
||||
wildcard: '%QUERY',
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
// Take all that crazy setup data and put it together into one beautiful typeahead call!
|
||||
$('.sidebarSearchField').typeahead(
|
||||
{
|
||||
highlight: true,
|
||||
},
|
||||
[topics, maps, mappers]
|
||||
);
|
||||
|
||||
//Set max height of the search results box to prevent it from covering bottom left footer
|
||||
$('.sidebarSearchField').bind('typeahead:render', function (event) {
|
||||
self.initSearchOptions();
|
||||
self.hideLoader();
|
||||
var h = $(window).height();
|
||||
$(".tt-dropdown-menu").css('max-height', h - 100);
|
||||
if (self.limitTopicsToMe) {
|
||||
$('#limitTopicsToMe').prop('checked', true);
|
||||
}
|
||||
if (self.limitMapsToMe) {
|
||||
$('#limitMapsToMe').prop('checked', true);
|
||||
}
|
||||
});
|
||||
$(window).resize(function () {
|
||||
var h = $(window).height();
|
||||
$(".tt-dropdown-menu").css('max-height', h - 100);
|
||||
});
|
||||
|
||||
// tell the autocomplete to launch a new tab with the topic, map, or mapper you clicked on
|
||||
$('.sidebarSearchField').bind('typeahead:select', self.handleResultClick);
|
||||
|
||||
// don't do it, if they clicked on a 'addToMap' button
|
||||
$('.sidebarSearch button.addToMap').click(function (event) {
|
||||
event.stopPropagation();
|
||||
});
|
||||
|
||||
// make sure that when you click on 'limit to me' or 'toggle section' it works
|
||||
$('.sidebarSearchField.tt-input').keyup(function(){
|
||||
if ($('.sidebarSearchField.tt-input').val() === '') {
|
||||
self.hideLoader();
|
||||
} else {
|
||||
self.showLoader();
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
handleResultClick: function (event, datum, dataset) {
|
||||
var self = Metamaps.GlobalUI.Search;
|
||||
|
||||
self.hideLoader();
|
||||
|
||||
if (["topic", "map", "mapper"].indexOf(datum.rtype) !== -1) {
|
||||
self.close(0, true);
|
||||
var win;
|
||||
if (datum.rtype == "topic") {
|
||||
Metamaps.Router.topics(datum.id);
|
||||
} else if (datum.rtype == "map") {
|
||||
Metamaps.Router.maps(datum.id);
|
||||
} else if (datum.rtype == "mapper") {
|
||||
Metamaps.Router.explore("mapper", datum.id);
|
||||
}
|
||||
}
|
||||
},
|
||||
initSearchOptions: function () {
|
||||
var self = Metamaps.GlobalUI.Search;
|
||||
|
||||
function toggleResultSet(set) {
|
||||
var s = $('.tt-dataset-' + set + ' .tt-suggestion, .tt-dataset-' + set + ' .resultnoresult');
|
||||
if (s.is(':visible')) {
|
||||
s.hide();
|
||||
$(this).removeClass('minimizeResults').addClass('maximizeResults');
|
||||
} else {
|
||||
s.show();
|
||||
$(this).removeClass('maximizeResults').addClass('minimizeResults');
|
||||
}
|
||||
}
|
||||
|
||||
$('.limitToMe').unbind().bind("change", function (e) {
|
||||
if ($(this).attr('id') == 'limitTopicsToMe') {
|
||||
self.limitTopicsToMe = !self.limitTopicsToMe;
|
||||
}
|
||||
if ($(this).attr('id') == 'limitMapsToMe') {
|
||||
self.limitMapsToMe = !self.limitMapsToMe;
|
||||
}
|
||||
|
||||
// set the value of the search equal to itself to retrigger the
|
||||
// autocomplete event
|
||||
var searchQuery = $('.sidebarSearchField.tt-input').val();
|
||||
$(".sidebarSearchField").typeahead('val', '')
|
||||
.typeahead('val', searchQuery);
|
||||
});
|
||||
|
||||
// when the user clicks minimize section, hide the results for that section
|
||||
$('.minimizeMapperResults').unbind().click(function (e) {
|
||||
toggleResultSet.call(this, 'mappers');
|
||||
});
|
||||
$('.minimizeTopicResults').unbind().click(function (e) {
|
||||
toggleResultSet.call(this, 'topics');
|
||||
});
|
||||
$('.minimizeMapResults').unbind().click(function (e) {
|
||||
toggleResultSet.call(this, 'maps');
|
||||
});
|
||||
},
|
||||
hideLoader: function () {
|
||||
$('#searchLoading').hide();
|
||||
},
|
||||
showLoader: function () {
|
||||
$('#searchLoading').show();
|
||||
}
|
||||
};
|
|
@ -1,326 +0,0 @@
|
|||
/* global Metamaps, $ */
|
||||
|
||||
/*
|
||||
* Metamaps.Import.js.erb
|
||||
*
|
||||
* Dependencies:
|
||||
* - Metamaps.Active
|
||||
* - Metamaps.Backbone
|
||||
* - Metamaps.Famous // TODO remove dependency
|
||||
* - Metamaps.Map
|
||||
* - Metamaps.Mappings
|
||||
* - Metamaps.Metacodes
|
||||
* - Metamaps.Synapses
|
||||
* - Metamaps.Topics
|
||||
*/
|
||||
|
||||
Metamaps.Import = {
|
||||
// note that user is not imported
|
||||
topicWhitelist: [
|
||||
'id', 'name', 'metacode', 'x', 'y', 'description', 'link', 'permission'
|
||||
],
|
||||
synapseWhitelist: [
|
||||
'topic1', 'topic2', 'category', 'desc', 'description', 'permission'
|
||||
],
|
||||
cidMappings: {}, // to be filled by import_id => cid mappings
|
||||
|
||||
init: function () {
|
||||
var self = Metamaps.Import
|
||||
|
||||
$('body').bind('paste', function (e) {
|
||||
if (e.target.tagName === 'INPUT') return
|
||||
if (e.target.tagName === 'TEXTAREA') return
|
||||
|
||||
var text = e.originalEvent.clipboardData.getData('text/plain')
|
||||
|
||||
var results
|
||||
if (text.trimLeft()[0] === '{') {
|
||||
try {
|
||||
results = JSON.parse(text)
|
||||
} catch (e) {
|
||||
results = false
|
||||
}
|
||||
} else {
|
||||
results = self.parseTabbedString(text)
|
||||
}
|
||||
if (results === false) return
|
||||
|
||||
var topics = results.topics
|
||||
var synapses = results.synapses
|
||||
|
||||
if (topics.length > 0 || synapses.length > 0) {
|
||||
if (window.confirm('Are you sure you want to create ' + topics.length +
|
||||
' new topics and ' + synapses.length + ' new synapses?')) {
|
||||
self.importTopics(topics)
|
||||
self.importSynapses(synapses)
|
||||
} // if
|
||||
} // if
|
||||
})
|
||||
},
|
||||
|
||||
abort: function (message) {
|
||||
console.error(message)
|
||||
},
|
||||
|
||||
simplify: function (string) {
|
||||
return string
|
||||
.replace(/(^\s*|\s*$)/g, '')
|
||||
.toLowerCase()
|
||||
},
|
||||
|
||||
parseTabbedString: function (text) {
|
||||
var self = Metamaps.Import
|
||||
|
||||
// determine line ending and split lines
|
||||
var delim = '\n'
|
||||
if (text.indexOf('\r\n') !== -1) {
|
||||
delim = '\r\n'
|
||||
} else if (text.indexOf('\r') !== -1) {
|
||||
delim = '\r'
|
||||
} // if
|
||||
|
||||
var STATES = {
|
||||
ABORT: -1,
|
||||
UNKNOWN: 0,
|
||||
TOPICS_NEED_HEADERS: 1,
|
||||
SYNAPSES_NEED_HEADERS: 2,
|
||||
TOPICS: 3,
|
||||
SYNAPSES: 4
|
||||
}
|
||||
|
||||
// state & lines determine parser behaviour
|
||||
var state = STATES.UNKNOWN
|
||||
var lines = text.split(delim)
|
||||
var results = { topics: [], synapses: [] }
|
||||
var topicHeaders = []
|
||||
var synapseHeaders = []
|
||||
|
||||
lines.forEach(function (line_raw, index) {
|
||||
var line = line_raw.split('\t')
|
||||
var noblanks = line.filter(function (elt) {
|
||||
return elt !== ''
|
||||
})
|
||||
switch (state) {
|
||||
case STATES.UNKNOWN:
|
||||
if (noblanks.length === 0) {
|
||||
state = STATES.UNKNOWN
|
||||
break
|
||||
} else if (noblanks.length === 1 && self.simplify(line[0]) === 'topics') {
|
||||
state = STATES.TOPICS_NEED_HEADERS
|
||||
break
|
||||
} else if (noblanks.length === 1 && self.simplify(line[0]) === 'synapses') {
|
||||
state = STATES.SYNAPSES_NEED_HEADERS
|
||||
break
|
||||
}
|
||||
state = STATES.TOPICS_NEED_HEADERS
|
||||
// FALL THROUGH - if we're not sure what to do, pretend
|
||||
// we're on the TOPICS_NEED_HEADERS state and parse some headers
|
||||
|
||||
case STATES.TOPICS_NEED_HEADERS: // eslint-disable-line
|
||||
if (noblanks.length < 2) {
|
||||
self.abort('Not enough topic headers on line ' + index)
|
||||
state = STATES.ABORT
|
||||
}
|
||||
topicHeaders = line.map(function (header, index) {
|
||||
return header.toLowerCase().replace('description', 'desc')
|
||||
})
|
||||
state = STATES.TOPICS
|
||||
break
|
||||
|
||||
case STATES.SYNAPSES_NEED_HEADERS:
|
||||
if (noblanks.length < 2) {
|
||||
self.abort('Not enough synapse headers on line ' + index)
|
||||
state = STATES.ABORT
|
||||
}
|
||||
synapseHeaders = line.map(function (header, index) {
|
||||
return header.toLowerCase().replace('description', 'desc')
|
||||
})
|
||||
state = STATES.SYNAPSES
|
||||
break
|
||||
|
||||
case STATES.TOPICS:
|
||||
if (noblanks.length === 0) {
|
||||
state = STATES.UNKNOWN
|
||||
} else if (noblanks.length === 1 && line[0].toLowerCase() === 'topics') {
|
||||
state = STATES.TOPICS_NEED_HEADERS
|
||||
} else if (noblanks.length === 1 && line[0].toLowerCase() === 'synapses') {
|
||||
state = STATES.SYNAPSES_NEED_HEADERS
|
||||
} else {
|
||||
var topic = {}
|
||||
line.forEach(function (field, index) {
|
||||
var header = topicHeaders[index]
|
||||
if (self.topicWhitelist.indexOf(header) === -1) return
|
||||
topic[header] = field
|
||||
if (['id', 'x', 'y'].indexOf(header) !== -1) {
|
||||
topic[header] = parseInt(topic[header])
|
||||
} // if
|
||||
})
|
||||
results.topics.push(topic)
|
||||
}
|
||||
break
|
||||
|
||||
case STATES.SYNAPSES:
|
||||
if (noblanks.length === 0) {
|
||||
state = STATES.UNKNOWN
|
||||
} else if (noblanks.length === 1 && line[0].toLowerCase() === 'topics') {
|
||||
state = STATES.TOPICS_NEED_HEADERS
|
||||
} else if (noblanks.length === 1 && line[0].toLowerCase() === 'synapses') {
|
||||
state = STATES.SYNAPSES_NEED_HEADERS
|
||||
} else {
|
||||
var synapse = {}
|
||||
line.forEach(function (field, index) {
|
||||
var header = synapseHeaders[index]
|
||||
if (self.synapseWhitelist.indexOf(header) === -1) return
|
||||
synapse[header] = field
|
||||
if (['id', 'topic1', 'topic2'].indexOf(header) !== -1) {
|
||||
synapse[header] = parseInt(synapse[header])
|
||||
} // if
|
||||
})
|
||||
results.synapses.push(synapse)
|
||||
}
|
||||
break
|
||||
case STATES.ABORT:
|
||||
|
||||
default:
|
||||
self.abort('Invalid state while parsing import data. Check code.')
|
||||
state = STATES.ABORT
|
||||
}
|
||||
})
|
||||
|
||||
if (state === STATES.ABORT) {
|
||||
return false
|
||||
} else {
|
||||
return results
|
||||
}
|
||||
},
|
||||
|
||||
importTopics: function (parsedTopics) {
|
||||
var self = Metamaps.Import
|
||||
|
||||
// up to 25 topics: scale 100
|
||||
// up to 81 topics: scale 200
|
||||
// up to 169 topics: scale 300
|
||||
var scale = Math.floor((Math.sqrt(parsedTopics.length) - 1) / 4) * 100
|
||||
if (scale < 100) scale = 100
|
||||
var autoX = -scale
|
||||
var autoY = -scale
|
||||
|
||||
parsedTopics.forEach(function (topic) {
|
||||
var x, y
|
||||
if (topic.x && topic.y) {
|
||||
x = topic.x
|
||||
y = topic.y
|
||||
} else {
|
||||
x = autoX
|
||||
y = autoY
|
||||
autoX += 50
|
||||
if (autoX > scale) {
|
||||
autoY += 50
|
||||
autoX = -scale
|
||||
}
|
||||
}
|
||||
|
||||
self.createTopicWithParameters(
|
||||
topic.name, topic.metacode, topic.permission,
|
||||
topic.desc, topic.link, x, y, topic.id
|
||||
)
|
||||
})
|
||||
},
|
||||
|
||||
importSynapses: function (parsedSynapses) {
|
||||
var self = Metamaps.Import
|
||||
|
||||
parsedSynapses.forEach(function (synapse) {
|
||||
// only createSynapseWithParameters once both topics are persisted
|
||||
var topic1 = Metamaps.Topics.get(self.cidMappings[synapse.topic1])
|
||||
var topic2 = Metamaps.Topics.get(self.cidMappings[synapse.topic2])
|
||||
if (!topic1 || !topic2) {
|
||||
console.error("One of the two topics doesn't exist!")
|
||||
console.error(synapse)
|
||||
return true
|
||||
}
|
||||
|
||||
var synapse_created = false
|
||||
topic1.once('sync', function () {
|
||||
if (topic1.id && topic2.id && !synapse_created) {
|
||||
synapse_created = true
|
||||
self.createSynapseWithParameters(
|
||||
synapse.desc, synapse.category, synapse.permission,
|
||||
topic1, topic2
|
||||
)
|
||||
} // if
|
||||
})
|
||||
topic2.once('sync', function () {
|
||||
if (topic1.id && topic2.id && !synapse_created) {
|
||||
synapse_created = true
|
||||
self.createSynapseWithParameters(
|
||||
synapse.desc, synapse.category, synapse.permission,
|
||||
topic1, topic2
|
||||
)
|
||||
} // if
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
createTopicWithParameters: function (name, metacode_name, permission, desc,
|
||||
link, xloc, yloc, import_id) {
|
||||
var self = Metamaps.Import
|
||||
$(document).trigger(Metamaps.Map.events.editedByActiveMapper)
|
||||
var metacode = Metamaps.Metacodes.where({name: metacode_name})[0] || null
|
||||
if (metacode === null) {
|
||||
metacode = Metamaps.Metacodes.where({ name: 'Wildcard' })[0]
|
||||
console.warn("Couldn't find metacode " + metacode_name + ' so used Wildcard instead.')
|
||||
}
|
||||
|
||||
var topic = new Metamaps.Backbone.Topic({
|
||||
name: name,
|
||||
metacode_id: metacode.id,
|
||||
permission: permission || Metamaps.Active.Map.get('permission'),
|
||||
desc: desc || "",
|
||||
link: link
|
||||
})
|
||||
Metamaps.Topics.add(topic)
|
||||
self.cidMappings[import_id] = topic.cid
|
||||
|
||||
var mapping = new Metamaps.Backbone.Mapping({
|
||||
xloc: xloc,
|
||||
yloc: yloc,
|
||||
mappable_id: topic.cid,
|
||||
mappable_type: 'Topic'
|
||||
})
|
||||
Metamaps.Mappings.add(mapping)
|
||||
|
||||
// this function also includes the creation of the topic in the database
|
||||
Metamaps.Topic.renderTopic(mapping, topic, true, true)
|
||||
|
||||
Metamaps.Famous.viz.hideInstructions()
|
||||
},
|
||||
|
||||
createSynapseWithParameters: function (desc, category, permission,
|
||||
topic1, topic2) {
|
||||
var node1 = topic1.get('node')
|
||||
var node2 = topic2.get('node')
|
||||
|
||||
if (!topic1.id || !topic2.id) {
|
||||
console.error('missing topic id when creating synapse')
|
||||
return
|
||||
} // if
|
||||
|
||||
var synapse = new Metamaps.Backbone.Synapse({
|
||||
desc: desc || "",
|
||||
category: category,
|
||||
permission: permission,
|
||||
node1_id: topic1.id,
|
||||
node2_id: topic2.id
|
||||
})
|
||||
Metamaps.Synapses.add(synapse)
|
||||
|
||||
var mapping = new Metamaps.Backbone.Mapping({
|
||||
mappable_type: 'Synapse',
|
||||
mappable_id: synapse.cid
|
||||
})
|
||||
Metamaps.Mappings.add(mapping)
|
||||
|
||||
Metamaps.Synapse.renderSynapse(mapping, synapse, node1, node2, true)
|
||||
}
|
||||
}
|
|
@ -1,122 +0,0 @@
|
|||
/* global Metamaps, $ */
|
||||
|
||||
/*
|
||||
* Metamaps.Listeners.js.erb
|
||||
*
|
||||
* Dependencies:
|
||||
* - Metamaps.Active
|
||||
* - Metamaps.Control
|
||||
* - Metamaps.JIT
|
||||
* - Metamaps.Visualize
|
||||
*/
|
||||
Metamaps.Listeners = {
|
||||
init: function () {
|
||||
var self = this
|
||||
$(document).on('keydown', function (e) {
|
||||
if (!(Metamaps.Active.Map || Metamaps.Active.Topic)) return
|
||||
|
||||
switch (e.which) {
|
||||
case 13: // if enter key is pressed
|
||||
Metamaps.JIT.enterKeyHandler()
|
||||
e.preventDefault()
|
||||
break
|
||||
case 27: // if esc key is pressed
|
||||
Metamaps.JIT.escKeyHandler()
|
||||
break
|
||||
case 65: // if a or A is pressed
|
||||
if (e.ctrlKey) {
|
||||
Metamaps.Control.deselectAllNodes()
|
||||
Metamaps.Control.deselectAllEdges()
|
||||
|
||||
e.preventDefault()
|
||||
Metamaps.Visualize.mGraph.graph.eachNode(function (n) {
|
||||
Metamaps.Control.selectNode(n, e)
|
||||
})
|
||||
|
||||
Metamaps.Visualize.mGraph.plot()
|
||||
}
|
||||
|
||||
break
|
||||
case 68: // if d or D is pressed
|
||||
if (e.ctrlKey) {
|
||||
e.preventDefault()
|
||||
Metamaps.Control.deleteSelected()
|
||||
}
|
||||
break
|
||||
case 69: // if e or E is pressed
|
||||
if (e.ctrlKey && Metamaps.Active.Map) {
|
||||
e.preventDefault()
|
||||
Metamaps.JIT.zoomExtents(null, Metamaps.Visualize.mGraph.canvas)
|
||||
break
|
||||
}
|
||||
if (e.altKey && Metamaps.Active.Topic) {
|
||||
e.preventDefault()
|
||||
|
||||
if (Metamaps.Active.Topic) {
|
||||
self.centerAndReveal(Metamaps.Selected.Nodes, {
|
||||
center: true,
|
||||
reveal: false
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
break
|
||||
case 72: // if h or H is pressed
|
||||
if (e.ctrlKey) {
|
||||
e.preventDefault()
|
||||
Metamaps.Control.hideSelectedNodes()
|
||||
Metamaps.Control.hideSelectedEdges()
|
||||
}
|
||||
break
|
||||
case 77: // if m or M is pressed
|
||||
if (e.ctrlKey) {
|
||||
e.preventDefault()
|
||||
Metamaps.Control.removeSelectedNodes()
|
||||
Metamaps.Control.removeSelectedEdges()
|
||||
}
|
||||
break
|
||||
case 82: // if r or R is pressed
|
||||
if (e.altKey && Metamaps.Active.Topic) {
|
||||
e.preventDefault()
|
||||
self.centerAndReveal(Metamaps.Selected.Nodes, {
|
||||
center: false,
|
||||
reveal: true
|
||||
})
|
||||
}
|
||||
break
|
||||
case 84: // if t or T is pressed
|
||||
if (e.altKey && Metamaps.Active.Topic) {
|
||||
e.preventDefault()
|
||||
self.centerAndReveal(Metamaps.Selected.Nodes, {
|
||||
center: true,
|
||||
reveal: true
|
||||
})
|
||||
}
|
||||
break
|
||||
default:
|
||||
// console.log(e.which)
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
$(window).resize(function () {
|
||||
if (Metamaps.Visualize && Metamaps.Visualize.mGraph) Metamaps.Visualize.mGraph.canvas.resize($(window).width(), $(window).height())
|
||||
if ((Metamaps.Active.Map || Metamaps.Active.Topic) && Metamaps.Famous && Metamaps.Famous.maps.surf) Metamaps.Famous.maps.reposition()
|
||||
if (Metamaps.Active.Map && Metamaps.Realtime.inConversation) Metamaps.Realtime.positionVideos()
|
||||
Metamaps.Mobile.resizeTitle()
|
||||
})
|
||||
},
|
||||
centerAndReveal: function(nodes, opts) {
|
||||
if (nodes.length < 1) return
|
||||
var node = nodes[nodes.length - 1]
|
||||
if (opts.center && opts.reveal) {
|
||||
Metamaps.Topic.centerOn(node.id, function() {
|
||||
Metamaps.Topic.fetchRelatives(nodes)
|
||||
})
|
||||
} else if (opts.center) {
|
||||
Metamaps.Topic.centerOn(node.id)
|
||||
} else if (opts.reveal) {
|
||||
Metamaps.Topic.fetchRelatives(nodes)
|
||||
}
|
||||
}
|
||||
}; // end Metamaps.Listeners
|
|
@ -1,816 +0,0 @@
|
|||
/* global Metamaps, $ */
|
||||
|
||||
/*
|
||||
* Metamaps.Map.js.erb
|
||||
*
|
||||
* Dependencies:
|
||||
* - Metamaps.Create
|
||||
* - Metamaps.Erb
|
||||
* - Metamaps.Filter
|
||||
* - Metamaps.JIT
|
||||
* - Metamaps.Loading
|
||||
* - Metamaps.Maps
|
||||
* - Metamaps.Realtime
|
||||
* - Metamaps.Router
|
||||
* - Metamaps.Selected
|
||||
* - Metamaps.SynapseCard
|
||||
* - Metamaps.TopicCard
|
||||
* - Metamaps.Visualize
|
||||
* - Metamaps.Active
|
||||
* - Metamaps.Backbone
|
||||
* - Metamaps.GlobalUI
|
||||
* - Metamaps.Mappers
|
||||
* - Metamaps.Mappings
|
||||
* - Metamaps.Messages
|
||||
* - Metamaps.Synapses
|
||||
* - Metamaps.Topics
|
||||
*
|
||||
* Major sub-modules:
|
||||
* - Metamaps.Map.CheatSheet
|
||||
* - Metamaps.Map.InfoBox
|
||||
*/
|
||||
|
||||
Metamaps.Map = {
|
||||
events: {
|
||||
editedByActiveMapper: 'Metamaps:Map:events:editedByActiveMapper'
|
||||
},
|
||||
nextX: 0,
|
||||
nextY: 0,
|
||||
sideLength: 1,
|
||||
turnCount: 0,
|
||||
nextXshift: 1,
|
||||
nextYshift: 0,
|
||||
timeToTurn: 0,
|
||||
init: function () {
|
||||
var self = Metamaps.Map
|
||||
|
||||
// prevent right clicks on the main canvas, so as to not get in the way of our right clicks
|
||||
$('#center-container').bind('contextmenu', function (e) {
|
||||
return false
|
||||
})
|
||||
|
||||
$('.starMap').click(function () {
|
||||
if ($(this).is('.starred')) self.unstar()
|
||||
else self.star()
|
||||
})
|
||||
|
||||
$('.sidebarFork').click(function () {
|
||||
self.fork()
|
||||
})
|
||||
|
||||
Metamaps.GlobalUI.CreateMap.emptyForkMapForm = $('#fork_map').html()
|
||||
|
||||
self.updateStar()
|
||||
self.InfoBox.init()
|
||||
self.CheatSheet.init()
|
||||
|
||||
$(document).on(Metamaps.Map.events.editedByActiveMapper, self.editedByActiveMapper)
|
||||
},
|
||||
launch: function (id) {
|
||||
var bb = Metamaps.Backbone
|
||||
var start = function (data) {
|
||||
Metamaps.Active.Map = new bb.Map(data.map)
|
||||
Metamaps.Mappers = new bb.MapperCollection(data.mappers)
|
||||
Metamaps.Collaborators = new bb.MapperCollection(data.collaborators)
|
||||
Metamaps.Topics = new bb.TopicCollection(data.topics)
|
||||
Metamaps.Synapses = new bb.SynapseCollection(data.synapses)
|
||||
Metamaps.Mappings = new bb.MappingCollection(data.mappings)
|
||||
Metamaps.Messages = data.messages
|
||||
Metamaps.Stars = data.stars
|
||||
Metamaps.Backbone.attachCollectionEvents()
|
||||
|
||||
var map = Metamaps.Active.Map
|
||||
var mapper = Metamaps.Active.Mapper
|
||||
|
||||
// add class to .wrapper for specifying whether you can edit the map
|
||||
if (map.authorizeToEdit(mapper)) {
|
||||
$('.wrapper').addClass('canEditMap')
|
||||
}
|
||||
|
||||
// add class to .wrapper for specifying if the map can
|
||||
// be collaborated on
|
||||
if (map.get('permission') === 'commons') {
|
||||
$('.wrapper').addClass('commonsMap')
|
||||
}
|
||||
|
||||
Metamaps.Map.updateStar()
|
||||
|
||||
// set filter mapper H3 text
|
||||
$('#filter_by_mapper h3').html('MAPPERS')
|
||||
|
||||
// build and render the visualization
|
||||
Metamaps.Visualize.type = 'ForceDirected'
|
||||
Metamaps.JIT.prepareVizData()
|
||||
|
||||
// update filters
|
||||
Metamaps.Filter.reset()
|
||||
|
||||
// reset selected arrays
|
||||
Metamaps.Selected.reset()
|
||||
|
||||
// set the proper mapinfobox content
|
||||
Metamaps.Map.InfoBox.load()
|
||||
|
||||
// these three update the actual filter box with the right list items
|
||||
Metamaps.Filter.checkMetacodes()
|
||||
Metamaps.Filter.checkSynapses()
|
||||
Metamaps.Filter.checkMappers()
|
||||
|
||||
Metamaps.Realtime.startActiveMap()
|
||||
Metamaps.Loading.hide()
|
||||
|
||||
// for mobile
|
||||
$('#header_content').html(map.get('name'))
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: '/maps/' + id + '/contains.json',
|
||||
success: start
|
||||
})
|
||||
},
|
||||
end: function () {
|
||||
if (Metamaps.Active.Map) {
|
||||
$('.wrapper').removeClass('canEditMap commonsMap')
|
||||
Metamaps.Map.resetSpiral()
|
||||
|
||||
$('.rightclickmenu').remove()
|
||||
Metamaps.TopicCard.hideCard()
|
||||
Metamaps.SynapseCard.hideCard()
|
||||
Metamaps.Create.newTopic.hide()
|
||||
Metamaps.Create.newSynapse.hide()
|
||||
Metamaps.Filter.close()
|
||||
Metamaps.Map.InfoBox.close()
|
||||
Metamaps.Realtime.endActiveMap()
|
||||
}
|
||||
},
|
||||
updateStar: function () {
|
||||
if (!Metamaps.Active.Mapper || !Metamaps.Stars) return
|
||||
// update the star/unstar icon
|
||||
if (Metamaps.Stars.find(function (s) { return s.user_id === Metamaps.Active.Mapper.id })) {
|
||||
$('.starMap').addClass('starred')
|
||||
$('.starMap .tooltipsAbove').html('Unstar')
|
||||
} else {
|
||||
$('.starMap').removeClass('starred')
|
||||
$('.starMap .tooltipsAbove').html('Star')
|
||||
}
|
||||
},
|
||||
star: function () {
|
||||
var self = Metamaps.Map
|
||||
|
||||
if (!Metamaps.Active.Map) return
|
||||
$.post('/maps/' + Metamaps.Active.Map.id + '/star')
|
||||
Metamaps.Stars.push({ user_id: Metamaps.Active.Mapper.id, map_id: Metamaps.Active.Map.id })
|
||||
Metamaps.Maps.Starred.add(Metamaps.Active.Map)
|
||||
Metamaps.GlobalUI.notifyUser('Map is now starred')
|
||||
self.updateStar()
|
||||
},
|
||||
unstar: function () {
|
||||
var self = Metamaps.Map
|
||||
|
||||
if (!Metamaps.Active.Map) return
|
||||
$.post('/maps/' + Metamaps.Active.Map.id + '/unstar')
|
||||
Metamaps.Stars = Metamaps.Stars.filter(function (s) { return s.user_id != Metamaps.Active.Mapper.id })
|
||||
Metamaps.Maps.Starred.remove(Metamaps.Active.Map)
|
||||
self.updateStar()
|
||||
},
|
||||
fork: function () {
|
||||
Metamaps.GlobalUI.openLightbox('forkmap')
|
||||
|
||||
var nodes_data = '',
|
||||
synapses_data = ''
|
||||
var nodes_array = []
|
||||
var synapses_array = []
|
||||
// collect the unfiltered topics
|
||||
Metamaps.Visualize.mGraph.graph.eachNode(function (n) {
|
||||
// if the opacity is less than 1 then it's filtered
|
||||
if (n.getData('alpha') === 1) {
|
||||
var id = n.getData('topic').id
|
||||
nodes_array.push(id)
|
||||
var x, y
|
||||
if (n.pos.x && n.pos.y) {
|
||||
x = n.pos.x
|
||||
y = n.pos.y
|
||||
} else {
|
||||
var x = Math.cos(n.pos.theta) * n.pos.rho
|
||||
var y = Math.sin(n.pos.theta) * n.pos.rho
|
||||
}
|
||||
nodes_data += id + '/' + x + '/' + y + ','
|
||||
}
|
||||
})
|
||||
// collect the unfiltered synapses
|
||||
Metamaps.Synapses.each(function (synapse) {
|
||||
var desc = synapse.get('desc')
|
||||
|
||||
var descNotFiltered = Metamaps.Filter.visible.synapses.indexOf(desc) > -1
|
||||
// make sure that both topics are being added, otherwise, it
|
||||
// doesn't make sense to add the synapse
|
||||
var topicsNotFiltered = nodes_array.indexOf(synapse.get('node1_id')) > -1
|
||||
topicsNotFiltered = topicsNotFiltered && nodes_array.indexOf(synapse.get('node2_id')) > -1
|
||||
if (descNotFiltered && topicsNotFiltered) {
|
||||
synapses_array.push(synapse.id)
|
||||
}
|
||||
})
|
||||
|
||||
synapses_data = synapses_array.join()
|
||||
nodes_data = nodes_data.slice(0, -1)
|
||||
|
||||
Metamaps.GlobalUI.CreateMap.topicsToMap = nodes_data
|
||||
Metamaps.GlobalUI.CreateMap.synapsesToMap = synapses_data
|
||||
},
|
||||
leavePrivateMap: function () {
|
||||
var map = Metamaps.Active.Map
|
||||
Metamaps.Maps.Active.remove(map)
|
||||
Metamaps.Maps.Featured.remove(map)
|
||||
Metamaps.Router.home()
|
||||
Metamaps.GlobalUI.notifyUser('Sorry! That map has been changed to Private.')
|
||||
},
|
||||
cantEditNow: function () {
|
||||
Metamaps.Realtime.turnOff(true); // true is for 'silence'
|
||||
Metamaps.GlobalUI.notifyUser('Map was changed to Public. Editing is disabled.')
|
||||
Metamaps.Active.Map.trigger('changeByOther')
|
||||
},
|
||||
canEditNow: function () {
|
||||
var confirmString = "You've been granted permission to edit this map. "
|
||||
confirmString += 'Do you want to reload and enable realtime collaboration?'
|
||||
var c = confirm(confirmString)
|
||||
if (c) {
|
||||
Metamaps.Router.maps(Metamaps.Active.Map.id)
|
||||
}
|
||||
},
|
||||
editedByActiveMapper: function () {
|
||||
if (Metamaps.Active.Mapper) {
|
||||
Metamaps.Mappers.add(Metamaps.Active.Mapper)
|
||||
}
|
||||
},
|
||||
getNextCoord: function () {
|
||||
var self = Metamaps.Map
|
||||
var nextX = self.nextX
|
||||
var nextY = self.nextY
|
||||
|
||||
var DISTANCE_BETWEEN = 120
|
||||
|
||||
self.nextX = self.nextX + DISTANCE_BETWEEN * self.nextXshift
|
||||
self.nextY = self.nextY + DISTANCE_BETWEEN * self.nextYshift
|
||||
|
||||
self.timeToTurn += 1
|
||||
// if true, it's time to turn
|
||||
if (self.timeToTurn === self.sideLength) {
|
||||
self.turnCount += 1
|
||||
// if true, it's time to increase side length
|
||||
if (self.turnCount % 2 === 0) {
|
||||
self.sideLength += 1
|
||||
}
|
||||
self.timeToTurn = 0
|
||||
|
||||
// going right? turn down
|
||||
if (self.nextXshift == 1 && self.nextYshift == 0) {
|
||||
self.nextXshift = 0
|
||||
self.nextYshift = 1
|
||||
}
|
||||
// going down? turn left
|
||||
else if (self.nextXshift == 0 && self.nextYshift == 1) {
|
||||
self.nextXshift = -1
|
||||
self.nextYshift = 0
|
||||
}
|
||||
// going left? turn up
|
||||
else if (self.nextXshift == -1 && self.nextYshift == 0) {
|
||||
self.nextXshift = 0
|
||||
self.nextYshift = -1
|
||||
}
|
||||
// going up? turn right
|
||||
else if (self.nextXshift == 0 && self.nextYshift == -1) {
|
||||
self.nextXshift = 1
|
||||
self.nextYshift = 0
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
x: nextX,
|
||||
y: nextY
|
||||
}
|
||||
},
|
||||
resetSpiral: function () {
|
||||
Metamaps.Map.nextX = 0
|
||||
Metamaps.Map.nextY = 0
|
||||
Metamaps.Map.nextXshift = 1
|
||||
Metamaps.Map.nextYshift = 0
|
||||
Metamaps.Map.sideLength = 1
|
||||
Metamaps.Map.timeToTurn = 0
|
||||
Metamaps.Map.turnCount = 0
|
||||
},
|
||||
exportImage: function () {
|
||||
var canvas = {}
|
||||
|
||||
canvas.canvas = document.createElement('canvas')
|
||||
canvas.canvas.width = 1880 // 960
|
||||
canvas.canvas.height = 1260 // 630
|
||||
|
||||
canvas.scaleOffsetX = 1
|
||||
canvas.scaleOffsetY = 1
|
||||
canvas.translateOffsetY = 0
|
||||
canvas.translateOffsetX = 0
|
||||
canvas.denySelected = true
|
||||
|
||||
canvas.getSize = function () {
|
||||
if (this.size) return this.size
|
||||
var canvas = this.canvas
|
||||
return this.size = {
|
||||
width: canvas.width,
|
||||
height: canvas.height
|
||||
}
|
||||
}
|
||||
canvas.scale = function (x, y) {
|
||||
var px = this.scaleOffsetX * x,
|
||||
py = this.scaleOffsetY * y
|
||||
var dx = this.translateOffsetX * (x - 1) / px,
|
||||
dy = this.translateOffsetY * (y - 1) / py
|
||||
this.scaleOffsetX = px
|
||||
this.scaleOffsetY = py
|
||||
this.getCtx().scale(x, y)
|
||||
this.translate(dx, dy)
|
||||
}
|
||||
canvas.translate = function (x, y) {
|
||||
var sx = this.scaleOffsetX,
|
||||
sy = this.scaleOffsetY
|
||||
this.translateOffsetX += x * sx
|
||||
this.translateOffsetY += y * sy
|
||||
this.getCtx().translate(x, y)
|
||||
}
|
||||
canvas.getCtx = function () {
|
||||
return this.canvas.getContext('2d')
|
||||
}
|
||||
// center it
|
||||
canvas.getCtx().translate(1880 / 2, 1260 / 2)
|
||||
|
||||
var mGraph = Metamaps.Visualize.mGraph
|
||||
|
||||
var id = mGraph.root
|
||||
var root = mGraph.graph.getNode(id)
|
||||
var T = !!root.visited
|
||||
|
||||
// pass true to avoid basing it on a selection
|
||||
Metamaps.JIT.zoomExtents(null, canvas, true)
|
||||
|
||||
var c = canvas.canvas,
|
||||
ctx = canvas.getCtx(),
|
||||
scale = canvas.scaleOffsetX
|
||||
|
||||
// draw a grey background
|
||||
ctx.fillStyle = '#d8d9da'
|
||||
var xPoint = (-(c.width / scale) / 2) - (canvas.translateOffsetX / scale),
|
||||
yPoint = (-(c.height / scale) / 2) - (canvas.translateOffsetY / scale)
|
||||
ctx.fillRect(xPoint, yPoint, c.width / scale, c.height / scale)
|
||||
|
||||
// draw the graph
|
||||
mGraph.graph.eachNode(function (node) {
|
||||
var nodeAlpha = node.getData('alpha')
|
||||
node.eachAdjacency(function (adj) {
|
||||
var nodeTo = adj.nodeTo
|
||||
if (!!nodeTo.visited === T && node.drawn && nodeTo.drawn) {
|
||||
mGraph.fx.plotLine(adj, canvas)
|
||||
}
|
||||
})
|
||||
if (node.drawn) {
|
||||
mGraph.fx.plotNode(node, canvas)
|
||||
}
|
||||
if (!mGraph.labelsHidden) {
|
||||
if (node.drawn && nodeAlpha >= 0.95) {
|
||||
mGraph.labels.plotLabel(canvas, node)
|
||||
} else {
|
||||
mGraph.labels.hideLabel(node, false)
|
||||
}
|
||||
}
|
||||
node.visited = !T
|
||||
})
|
||||
|
||||
var imageData = {
|
||||
encoded_image: canvas.canvas.toDataURL()
|
||||
}
|
||||
|
||||
var map = Metamaps.Active.Map
|
||||
|
||||
var today = new Date()
|
||||
var dd = today.getDate()
|
||||
var mm = today.getMonth() + 1; // January is 0!
|
||||
var yyyy = today.getFullYear()
|
||||
if (dd < 10) {
|
||||
dd = '0' + dd
|
||||
}
|
||||
if (mm < 10) {
|
||||
mm = '0' + mm
|
||||
}
|
||||
today = mm + '/' + dd + '/' + yyyy
|
||||
|
||||
var mapName = map.get('name').split(' ').join([separator = '-'])
|
||||
var downloadMessage = ''
|
||||
downloadMessage += 'Captured map screenshot! '
|
||||
downloadMessage += "<a href='" + imageData.encoded_image + "' "
|
||||
downloadMessage += "download='metamap-" + map.id + '-' + mapName + '-' + today + ".png'>DOWNLOAD</a>"
|
||||
Metamaps.GlobalUI.notifyUser(downloadMessage)
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
url: '/maps/' + Metamaps.Active.Map.id + '/upload_screenshot',
|
||||
data: imageData,
|
||||
success: function (data) {
|
||||
console.log('successfully uploaded map screenshot')
|
||||
},
|
||||
error: function () {
|
||||
console.log('failed to save map screenshot')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* CHEATSHEET
|
||||
*
|
||||
*/
|
||||
Metamaps.Map.CheatSheet = {
|
||||
init: function () {
|
||||
// tab the cheatsheet
|
||||
$('#cheatSheet').tabs()
|
||||
$('#quickReference').tabs().addClass('ui-tabs-vertical ui-helper-clearfix')
|
||||
$('#quickReference .ui-tabs-nav li').removeClass('ui-corner-top').addClass('ui-corner-left')
|
||||
|
||||
// id = the id of a vimeo video
|
||||
var switchVideo = function (element, id) {
|
||||
$('.tutorialItem').removeClass('active')
|
||||
$(element).addClass('active')
|
||||
$('#tutorialVideo').attr('src', '//player.vimeo.com/video/' + id)
|
||||
}
|
||||
|
||||
$('#gettingStarted').click(function () {
|
||||
// switchVideo(this,'88334167')
|
||||
})
|
||||
$('#upYourSkillz').click(function () {
|
||||
// switchVideo(this,'100118167')
|
||||
})
|
||||
$('#advancedMapping').click(function () {
|
||||
// switchVideo(this,'88334167')
|
||||
})
|
||||
}
|
||||
}; // end Metamaps.Map.CheatSheet
|
||||
|
||||
/*
|
||||
*
|
||||
* INFOBOX
|
||||
*
|
||||
*/
|
||||
Metamaps.Map.InfoBox = {
|
||||
isOpen: false,
|
||||
changing: false,
|
||||
selectingPermission: false,
|
||||
changePermissionText: "<div class='tooltips'>As the creator, you can change the permission of this map, and the permission of all the topics and synapses you have authority to change will change as well.</div>",
|
||||
nameHTML: '<span class="best_in_place best_in_place_name" id="best_in_place_map_{{id}}_name" data-url="/maps/{{id}}" data-object="map" data-attribute="name" data-type="textarea" data-activator="#mapInfoName">{{name}}</span>',
|
||||
descHTML: '<span class="best_in_place best_in_place_desc" id="best_in_place_map_{{id}}_desc" data-url="/maps/{{id}}" data-object="map" data-attribute="desc" data-nil="Click to add description..." data-type="textarea" data-activator="#mapInfoDesc">{{desc}}</span>',
|
||||
init: function () {
|
||||
var self = Metamaps.Map.InfoBox
|
||||
|
||||
$('.mapInfoIcon').click(self.toggleBox)
|
||||
$('.mapInfoBox').click(function (event) {
|
||||
event.stopPropagation()
|
||||
})
|
||||
$('body').click(self.close)
|
||||
|
||||
self.attachEventListeners()
|
||||
|
||||
self.generateBoxHTML = Hogan.compile($('#mapInfoBoxTemplate').html())
|
||||
|
||||
var querystring = window.location.search.replace(/^\?/, '')
|
||||
if (querystring == 'new') {
|
||||
self.open()
|
||||
$('.mapInfoBox').addClass('mapRequestTitle')
|
||||
}
|
||||
},
|
||||
toggleBox: function (event) {
|
||||
var self = Metamaps.Map.InfoBox
|
||||
|
||||
if (self.isOpen) self.close()
|
||||
else self.open()
|
||||
|
||||
event.stopPropagation()
|
||||
},
|
||||
open: function () {
|
||||
var self = Metamaps.Map.InfoBox
|
||||
$('.mapInfoIcon div').addClass('hide')
|
||||
if (!self.isOpen && !self.changing) {
|
||||
self.changing = true
|
||||
$('.mapInfoBox').fadeIn(200, function () {
|
||||
self.changing = false
|
||||
self.isOpen = true
|
||||
})
|
||||
}
|
||||
},
|
||||
close: function () {
|
||||
var self = Metamaps.Map.InfoBox
|
||||
|
||||
$('.mapInfoIcon div').removeClass('hide')
|
||||
if (!self.changing) {
|
||||
self.changing = true
|
||||
$('.mapInfoBox').fadeOut(200, function () {
|
||||
self.changing = false
|
||||
self.isOpen = false
|
||||
self.hidePermissionSelect()
|
||||
$('.mapContributors .tip').hide()
|
||||
})
|
||||
}
|
||||
},
|
||||
load: function () {
|
||||
var self = Metamaps.Map.InfoBox
|
||||
|
||||
var map = Metamaps.Active.Map
|
||||
|
||||
var obj = map.pick('permission', 'topic_count', 'synapse_count')
|
||||
|
||||
var isCreator = map.authorizePermissionChange(Metamaps.Active.Mapper)
|
||||
var canEdit = map.authorizeToEdit(Metamaps.Active.Mapper)
|
||||
var relevantPeople = map.get('permission') === 'commons' ? Metamaps.Mappers : Metamaps.Collaborators
|
||||
var shareable = map.get('permission') !== 'private'
|
||||
|
||||
obj['name'] = canEdit ? Hogan.compile(self.nameHTML).render({id: map.id, name: map.get('name')}) : map.get('name')
|
||||
obj['desc'] = canEdit ? Hogan.compile(self.descHTML).render({id: map.id, desc: map.get('desc')}) : map.get('desc')
|
||||
obj['map_creator_tip'] = isCreator ? self.changePermissionText : ''
|
||||
|
||||
obj['contributor_count'] = relevantPeople.length
|
||||
obj['contributors_class'] = relevantPeople.length > 1 ? 'multiple' : ''
|
||||
obj['contributors_class'] += relevantPeople.length === 2 ? ' mTwo' : ''
|
||||
obj['contributor_image'] = relevantPeople.length > 0 ? relevantPeople.models[0].get('image') : Metamaps.Erb['user.png']
|
||||
obj['contributor_list'] = self.createContributorList()
|
||||
|
||||
obj['user_name'] = isCreator ? 'You' : map.get('user_name')
|
||||
obj['created_at'] = map.get('created_at_clean')
|
||||
obj['updated_at'] = map.get('updated_at_clean')
|
||||
|
||||
var classes = isCreator ? 'yourMap' : ''
|
||||
classes += canEdit ? ' canEdit' : ''
|
||||
classes += shareable ? ' shareable' : ''
|
||||
$('.mapInfoBox').removeClass('shareable yourMap canEdit')
|
||||
.addClass(classes)
|
||||
.html(self.generateBoxHTML.render(obj))
|
||||
|
||||
self.attachEventListeners()
|
||||
},
|
||||
attachEventListeners: function () {
|
||||
var self = Metamaps.Map.InfoBox
|
||||
|
||||
$('.mapInfoBox.canEdit .best_in_place').best_in_place()
|
||||
|
||||
// because anyone who can edit the map can change the map title
|
||||
var bipName = $('.mapInfoBox .best_in_place_name')
|
||||
bipName.unbind('best_in_place:activate').bind('best_in_place:activate', function () {
|
||||
var $el = bipName.find('textarea')
|
||||
var el = $el[0]
|
||||
|
||||
$el.attr('maxlength', '140')
|
||||
|
||||
$('.mapInfoName').append('<div class="nameCounter forMap"></div>')
|
||||
|
||||
var callback = function (data) {
|
||||
$('.nameCounter.forMap').html(data.all + '/140')
|
||||
}
|
||||
Countable.live(el, callback)
|
||||
})
|
||||
bipName.unbind('best_in_place:deactivate').bind('best_in_place:deactivate', function () {
|
||||
$('.nameCounter.forMap').remove()
|
||||
})
|
||||
|
||||
$('.mapInfoName .best_in_place_name').unbind('ajax:success').bind('ajax:success', function () {
|
||||
var name = $(this).html()
|
||||
Metamaps.Active.Map.set('name', name)
|
||||
Metamaps.Active.Map.trigger('saved')
|
||||
// mobile menu
|
||||
$('#header_content').html(name)
|
||||
$('.mapInfoBox').removeClass('mapRequestTitle')
|
||||
document.title = name + ' | Metamaps'
|
||||
})
|
||||
|
||||
$('.mapInfoDesc .best_in_place_desc').unbind('ajax:success').bind('ajax:success', function () {
|
||||
var desc = $(this).html()
|
||||
Metamaps.Active.Map.set('desc', desc)
|
||||
Metamaps.Active.Map.trigger('saved')
|
||||
})
|
||||
|
||||
$('.yourMap .mapPermission').unbind().click(self.onPermissionClick)
|
||||
// .yourMap in the unbind/bind is just a namespace for the events
|
||||
// not a reference to the class .yourMap on the .mapInfoBox
|
||||
$('.mapInfoBox.yourMap').unbind('.yourMap').bind('click.yourMap', self.hidePermissionSelect)
|
||||
|
||||
$('.yourMap .mapInfoDelete').unbind().click(self.deleteActiveMap)
|
||||
|
||||
$('.mapContributors span, #mapContribs').unbind().click(function (event) {
|
||||
$('.mapContributors .tip').toggle()
|
||||
event.stopPropagation()
|
||||
})
|
||||
$('.mapContributors .tip').unbind().click(function (event) {
|
||||
event.stopPropagation()
|
||||
})
|
||||
$('.mapContributors .tip li a').click(Metamaps.Router.intercept)
|
||||
|
||||
$('.mapInfoBox').unbind('.hideTip').bind('click.hideTip', function () {
|
||||
$('.mapContributors .tip').hide()
|
||||
})
|
||||
|
||||
self.addTypeahead()
|
||||
},
|
||||
addTypeahead: function () {
|
||||
var self = Metamaps.Map.InfoBox
|
||||
|
||||
if (!Metamaps.Active.Map) return
|
||||
|
||||
// for autocomplete
|
||||
var collaborators = {
|
||||
name: 'collaborators',
|
||||
limit: 9999,
|
||||
display: function(s) { return s.label; },
|
||||
templates: {
|
||||
notFound: function(s) {
|
||||
return Hogan.compile($('#collaboratorSearchTemplate').html()).render({
|
||||
value: "No results",
|
||||
label: "No results",
|
||||
rtype: "noresult",
|
||||
profile: Metamaps.Erb['user.png'],
|
||||
});
|
||||
},
|
||||
suggestion: function(s) {
|
||||
return Hogan.compile($('#collaboratorSearchTemplate').html()).render(s);
|
||||
},
|
||||
},
|
||||
source: new Bloodhound({
|
||||
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
|
||||
queryTokenizer: Bloodhound.tokenizers.whitespace,
|
||||
remote: {
|
||||
url: '/search/mappers?term=%QUERY',
|
||||
wildcard: '%QUERY',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// for adding map collaborators, who will have edit rights
|
||||
if (Metamaps.Active.Mapper && Metamaps.Active.Mapper.id === Metamaps.Active.Map.get('user_id')) {
|
||||
$('.collaboratorSearchField').typeahead(
|
||||
{
|
||||
highlight: false,
|
||||
},
|
||||
[collaborators]
|
||||
)
|
||||
$('.collaboratorSearchField').bind('typeahead:select', self.handleResultClick)
|
||||
$('.mapContributors .removeCollaborator').click(function () {
|
||||
self.removeCollaborator(parseInt($(this).data('id')))
|
||||
})
|
||||
}
|
||||
},
|
||||
removeCollaborator: function (collaboratorId) {
|
||||
var self = Metamaps.Map.InfoBox
|
||||
Metamaps.Collaborators.remove(Metamaps.Collaborators.get(collaboratorId))
|
||||
var mapperIds = Metamaps.Collaborators.models.map(function (mapper) { return mapper.id })
|
||||
$.post('/maps/' + Metamaps.Active.Map.id + '/access', { access: mapperIds })
|
||||
self.updateNumbers()
|
||||
},
|
||||
addCollaborator: function (newCollaboratorId) {
|
||||
var self = Metamaps.Map.InfoBox
|
||||
|
||||
if (Metamaps.Collaborators.get(newCollaboratorId)) {
|
||||
Metamaps.GlobalUI.notifyUser('That user already has access')
|
||||
return
|
||||
}
|
||||
|
||||
function callback(mapper) {
|
||||
Metamaps.Collaborators.add(mapper)
|
||||
var mapperIds = Metamaps.Collaborators.models.map(function (mapper) { return mapper.id })
|
||||
$.post('/maps/' + Metamaps.Active.Map.id + '/access', { access: mapperIds })
|
||||
var name = Metamaps.Collaborators.get(newCollaboratorId).get('name')
|
||||
Metamaps.GlobalUI.notifyUser(name + ' will be notified by email')
|
||||
self.updateNumbers()
|
||||
}
|
||||
|
||||
$.getJSON('/users/' + newCollaboratorId + '.json', callback)
|
||||
},
|
||||
handleResultClick: function (event, item) {
|
||||
var self = Metamaps.Map.InfoBox
|
||||
|
||||
self.addCollaborator(item.id)
|
||||
$('.collaboratorSearchField').typeahead('val', '')
|
||||
},
|
||||
updateNameDescPerm: function (name, desc, perm) {
|
||||
$('.mapInfoBox').removeClass('mapRequestTitle')
|
||||
$('.mapInfoName .best_in_place_name').html(name)
|
||||
$('.mapInfoDesc .best_in_place_desc').html(desc)
|
||||
$('.mapInfoBox .mapPermission').removeClass('commons public private').addClass(perm)
|
||||
},
|
||||
createContributorList: function () {
|
||||
var self = Metamaps.Map.InfoBox
|
||||
var relevantPeople = Metamaps.Active.Map.get('permission') === 'commons' ? Metamaps.Mappers : Metamaps.Collaborators
|
||||
var activeMapperIsCreator = Metamaps.Active.Mapper && Metamaps.Active.Mapper.id === Metamaps.Active.Map.get('user_id')
|
||||
var string = ''
|
||||
string += '<ul>'
|
||||
|
||||
relevantPeople.each(function (m) {
|
||||
var isCreator = Metamaps.Active.Map.get('user_id') === m.get('id')
|
||||
string += '<li><a href="/explore/mapper/' + m.get('id') + '">' + '<img class="rtUserImage" width="25" height="25" src="' + m.get('image') + '" />' + m.get('name')
|
||||
if (isCreator) string += ' (creator)'
|
||||
string += '</a>'
|
||||
if (activeMapperIsCreator && !isCreator) string += '<span class="removeCollaborator" data-id="' + m.get('id') + '"></span>'
|
||||
string += '</li>'
|
||||
})
|
||||
|
||||
string += '</ul>'
|
||||
|
||||
if (activeMapperIsCreator) {
|
||||
string += '<div class="collabSearchField"><span class="addCollab"></span><input class="collaboratorSearchField" placeholder="Add a collaborator!"></input></div>'
|
||||
}
|
||||
return string
|
||||
},
|
||||
updateNumbers: function () {
|
||||
if (!Metamaps.Active.Map) return
|
||||
|
||||
var self = Metamaps.Map.InfoBox
|
||||
var mapper = Metamaps.Active.Mapper
|
||||
var relevantPeople = Metamaps.Active.Map.get('permission') === 'commons' ? Metamaps.Mappers : Metamaps.Collaborators
|
||||
|
||||
var contributors_class = ''
|
||||
if (relevantPeople.length === 2) contributors_class = 'multiple mTwo'
|
||||
else if (relevantPeople.length > 2) contributors_class = 'multiple'
|
||||
|
||||
var contributors_image = Metamaps.Erb['user.png']
|
||||
if (relevantPeople.length > 0) {
|
||||
// get the first contributor and use their image
|
||||
contributors_image = relevantPeople.models[0].get('image')
|
||||
}
|
||||
$('.mapContributors img').attr('src', contributors_image).removeClass('multiple mTwo').addClass(contributors_class)
|
||||
$('.mapContributors span').text(relevantPeople.length)
|
||||
$('.mapContributors .tip').html(self.createContributorList())
|
||||
self.addTypeahead()
|
||||
$('.mapContributors .tip').unbind().click(function (event) {
|
||||
event.stopPropagation()
|
||||
})
|
||||
$('.mapTopics').text(Metamaps.Topics.length)
|
||||
$('.mapSynapses').text(Metamaps.Synapses.length)
|
||||
|
||||
$('.mapEditedAt').html('<span>Last edited: </span>' + Metamaps.Util.nowDateFormatted())
|
||||
},
|
||||
onPermissionClick: function (event) {
|
||||
var self = Metamaps.Map.InfoBox
|
||||
|
||||
if (!self.selectingPermission) {
|
||||
self.selectingPermission = true
|
||||
$(this).addClass('minimize') // this line flips the drop down arrow to a pull up arrow
|
||||
if ($(this).hasClass('commons')) {
|
||||
$(this).append('<ul class="permissionSelect"><li class="public"></li><li class="private"></li></ul>')
|
||||
} else if ($(this).hasClass('public')) {
|
||||
$(this).append('<ul class="permissionSelect"><li class="commons"></li><li class="private"></li></ul>')
|
||||
} else if ($(this).hasClass('private')) {
|
||||
$(this).append('<ul class="permissionSelect"><li class="commons"></li><li class="public"></li></ul>')
|
||||
}
|
||||
$('.mapPermission .permissionSelect li').click(self.selectPermission)
|
||||
event.stopPropagation()
|
||||
}
|
||||
},
|
||||
hidePermissionSelect: function () {
|
||||
var self = Metamaps.Map.InfoBox
|
||||
|
||||
self.selectingPermission = false
|
||||
$('.mapPermission').removeClass('minimize') // this line flips the pull up arrow to a drop down arrow
|
||||
$('.mapPermission .permissionSelect').remove()
|
||||
},
|
||||
selectPermission: function (event) {
|
||||
var self = Metamaps.Map.InfoBox
|
||||
|
||||
self.selectingPermission = false
|
||||
var permission = $(this).attr('class')
|
||||
Metamaps.Active.Map.save({
|
||||
permission: permission
|
||||
})
|
||||
Metamaps.Active.Map.updateMapWrapper()
|
||||
shareable = permission === 'private' ? '' : 'shareable'
|
||||
$('.mapPermission').removeClass('commons public private minimize').addClass(permission)
|
||||
$('.mapPermission .permissionSelect').remove()
|
||||
$('.mapInfoBox').removeClass('shareable').addClass(shareable)
|
||||
event.stopPropagation()
|
||||
},
|
||||
deleteActiveMap: function () {
|
||||
var confirmString = 'Are you sure you want to delete this map? '
|
||||
confirmString += 'This action is irreversible. It will not delete the topics and synapses on the map.'
|
||||
|
||||
var doIt = confirm(confirmString)
|
||||
var map = Metamaps.Active.Map
|
||||
var mapper = Metamaps.Active.Mapper
|
||||
var authorized = map.authorizePermissionChange(mapper)
|
||||
|
||||
if (doIt && authorized) {
|
||||
Metamaps.Map.InfoBox.close()
|
||||
Metamaps.Maps.Active.remove(map)
|
||||
Metamaps.Maps.Featured.remove(map)
|
||||
Metamaps.Maps.Mine.remove(map)
|
||||
Metamaps.Maps.Shared.remove(map)
|
||||
map.destroy()
|
||||
Metamaps.Router.home()
|
||||
Metamaps.GlobalUI.notifyUser('Map eliminated!')
|
||||
}
|
||||
else if (!authorized) {
|
||||
alert("Hey now. We can't just go around willy nilly deleting other people's maps now can we? Run off and find something constructive to do, eh?")
|
||||
}
|
||||
}
|
||||
}; // end Metamaps.Map.InfoBox
|
|
@ -1,245 +0,0 @@
|
|||
/* global Metamaps, Backbone, $ */
|
||||
|
||||
/*
|
||||
* Metamaps.Router.js.erb
|
||||
*
|
||||
* Dependencies:
|
||||
* - Metamaps.Active
|
||||
* - Metamaps.GlobalUI
|
||||
* - Metamaps.JIT
|
||||
* - Metamaps.Loading
|
||||
* - Metamaps.Map
|
||||
* - Metamaps.Maps
|
||||
* - Metamaps.Topic
|
||||
* - Metamaps.Views
|
||||
* - Metamaps.Visualize
|
||||
*/
|
||||
|
||||
;(function () {
|
||||
var Router = Backbone.Router.extend({
|
||||
routes: {
|
||||
'': 'home', // #home
|
||||
'explore/:section': 'explore', // #explore/active
|
||||
'explore/:section/:id': 'explore', // #explore/mapper/1234
|
||||
'maps/:id': 'maps' // #maps/7
|
||||
},
|
||||
home: function () {
|
||||
clearTimeout(Metamaps.Router.timeoutId)
|
||||
|
||||
if (Metamaps.Active.Mapper) document.title = 'Explore Active Maps | Metamaps'
|
||||
else document.title = 'Home | Metamaps'
|
||||
|
||||
Metamaps.Router.currentSection = ''
|
||||
Metamaps.Router.currentPage = ''
|
||||
$('.wrapper').removeClass('mapPage topicPage')
|
||||
|
||||
var classes = Metamaps.Active.Mapper ? 'homePage explorePage' : 'homePage'
|
||||
$('.wrapper').addClass(classes)
|
||||
|
||||
var navigate = function () {
|
||||
Metamaps.Router.timeoutId = setTimeout(function () {
|
||||
Metamaps.Router.navigate('')
|
||||
}, 300)
|
||||
}
|
||||
|
||||
// all this only for the logged in home page
|
||||
if (Metamaps.Active.Mapper) {
|
||||
$('.homeButton a').attr('href', '/')
|
||||
Metamaps.GlobalUI.hideDiv('#yield')
|
||||
|
||||
Metamaps.GlobalUI.showDiv('#explore')
|
||||
|
||||
Metamaps.Views.exploreMaps.setCollection(Metamaps.Maps.Active)
|
||||
if (Metamaps.Maps.Active.length === 0) {
|
||||
Metamaps.Maps.Active.getMaps(navigate) // this will trigger an explore maps render
|
||||
} else {
|
||||
Metamaps.Views.exploreMaps.render(navigate)
|
||||
}
|
||||
} else {
|
||||
// logged out home page
|
||||
Metamaps.GlobalUI.hideDiv('#explore')
|
||||
Metamaps.GlobalUI.showDiv('#yield')
|
||||
Metamaps.Router.timeoutId = setTimeout(navigate, 500)
|
||||
}
|
||||
|
||||
Metamaps.GlobalUI.hideDiv('#infovis')
|
||||
Metamaps.GlobalUI.hideDiv('#instructions')
|
||||
Metamaps.Map.end()
|
||||
Metamaps.Topic.end()
|
||||
Metamaps.Active.Map = null
|
||||
Metamaps.Active.Topic = null
|
||||
},
|
||||
explore: function (section, id) {
|
||||
clearTimeout(Metamaps.Router.timeoutId)
|
||||
|
||||
// just capitalize the variable section
|
||||
// either 'featured', 'mapper', or 'active'
|
||||
var capitalize = section.charAt(0).toUpperCase() + section.slice(1)
|
||||
|
||||
if (section === 'shared' || section === 'featured' || section === 'active' || section === 'starred') {
|
||||
document.title = 'Explore ' + capitalize + ' Maps | Metamaps'
|
||||
} else if (section === 'mapper') {
|
||||
$.ajax({
|
||||
url: '/users/' + id + '.json',
|
||||
success: function (response) {
|
||||
document.title = response.name + ' | Metamaps'
|
||||
},
|
||||
error: function () {}
|
||||
})
|
||||
} else if (section === 'mine') {
|
||||
document.title = 'Explore My Maps | Metamaps'
|
||||
}
|
||||
|
||||
if (Metamaps.Active.Mapper && section != 'mapper') $('.homeButton a').attr('href', '/explore/' + section)
|
||||
$('.wrapper').removeClass('homePage mapPage topicPage')
|
||||
$('.wrapper').addClass('explorePage')
|
||||
|
||||
Metamaps.Router.currentSection = 'explore'
|
||||
Metamaps.Router.currentPage = section
|
||||
|
||||
// this will mean it's a mapper page being loaded
|
||||
if (id) {
|
||||
if (Metamaps.Maps.Mapper.mapperId !== id) {
|
||||
// empty the collection if we are trying to load the maps
|
||||
// collection of a different mapper than we had previously
|
||||
Metamaps.Maps.Mapper.reset()
|
||||
Metamaps.Maps.Mapper.page = 1
|
||||
}
|
||||
Metamaps.Maps.Mapper.mapperId = id
|
||||
}
|
||||
|
||||
Metamaps.Views.exploreMaps.setCollection(Metamaps.Maps[capitalize])
|
||||
|
||||
var navigate = function () {
|
||||
var path = '/explore/' + Metamaps.Router.currentPage
|
||||
|
||||
// alter url if for mapper profile page
|
||||
if (Metamaps.Router.currentPage === 'mapper') {
|
||||
path += '/' + Metamaps.Maps.Mapper.mapperId
|
||||
}
|
||||
|
||||
Metamaps.Router.navigate(path)
|
||||
}
|
||||
var navigateTimeout = function () {
|
||||
Metamaps.Router.timeoutId = setTimeout(navigate, 300)
|
||||
}
|
||||
if (Metamaps.Maps[capitalize].length === 0) {
|
||||
Metamaps.Loading.show()
|
||||
setTimeout(function () {
|
||||
Metamaps.Maps[capitalize].getMaps(navigate) // this will trigger an explore maps render
|
||||
}, 300) // wait 300 milliseconds till the other animations are done to do the fetch
|
||||
} else {
|
||||
if (id) {
|
||||
Metamaps.Views.exploreMaps.fetchUserThenRender(navigateTimeout)
|
||||
} else {
|
||||
Metamaps.Views.exploreMaps.render(navigateTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
Metamaps.GlobalUI.showDiv('#explore')
|
||||
Metamaps.GlobalUI.hideDiv('#yield')
|
||||
Metamaps.GlobalUI.hideDiv('#infovis')
|
||||
Metamaps.GlobalUI.hideDiv('#instructions')
|
||||
Metamaps.Map.end()
|
||||
Metamaps.Topic.end()
|
||||
Metamaps.Active.Map = null
|
||||
Metamaps.Active.Topic = null
|
||||
},
|
||||
maps: function (id) {
|
||||
clearTimeout(Metamaps.Router.timeoutId)
|
||||
|
||||
document.title = 'Map ' + id + ' | Metamaps'
|
||||
|
||||
Metamaps.Router.currentSection = 'map'
|
||||
Metamaps.Router.currentPage = id
|
||||
|
||||
$('.wrapper').removeClass('homePage explorePage topicPage')
|
||||
$('.wrapper').addClass('mapPage')
|
||||
// another class will be added to wrapper if you
|
||||
// can edit this map '.canEditMap'
|
||||
|
||||
Metamaps.GlobalUI.hideDiv('#yield')
|
||||
Metamaps.GlobalUI.hideDiv('#explore')
|
||||
|
||||
// clear the visualization, if there was one, before showing its div again
|
||||
if (Metamaps.Visualize.mGraph) {
|
||||
Metamaps.Visualize.mGraph.graph.empty()
|
||||
Metamaps.Visualize.mGraph.plot()
|
||||
Metamaps.JIT.centerMap(Metamaps.Visualize.mGraph.canvas)
|
||||
}
|
||||
Metamaps.GlobalUI.showDiv('#infovis')
|
||||
Metamaps.Topic.end()
|
||||
Metamaps.Active.Topic = null
|
||||
|
||||
Metamaps.Loading.show()
|
||||
Metamaps.Map.end()
|
||||
Metamaps.Map.launch(id)
|
||||
},
|
||||
topics: function (id) {
|
||||
clearTimeout(Metamaps.Router.timeoutId)
|
||||
|
||||
document.title = 'Topic ' + id + ' | Metamaps'
|
||||
|
||||
Metamaps.Router.currentSection = 'topic'
|
||||
Metamaps.Router.currentPage = id
|
||||
|
||||
$('.wrapper').removeClass('homePage explorePage mapPage')
|
||||
$('.wrapper').addClass('topicPage')
|
||||
|
||||
Metamaps.GlobalUI.hideDiv('#yield')
|
||||
Metamaps.GlobalUI.hideDiv('#explore')
|
||||
|
||||
// clear the visualization, if there was one, before showing its div again
|
||||
if (Metamaps.Visualize.mGraph) {
|
||||
Metamaps.Visualize.mGraph.graph.empty()
|
||||
Metamaps.Visualize.mGraph.plot()
|
||||
Metamaps.JIT.centerMap(Metamaps.Visualize.mGraph.canvas)
|
||||
}
|
||||
Metamaps.GlobalUI.showDiv('#infovis')
|
||||
Metamaps.Map.end()
|
||||
Metamaps.Active.Map = null
|
||||
|
||||
Metamaps.Topic.end()
|
||||
Metamaps.Topic.launch(id)
|
||||
}
|
||||
})
|
||||
|
||||
Metamaps.Router = new Router()
|
||||
Metamaps.Router.currentPage = ''
|
||||
Metamaps.Router.currentSection = undefined
|
||||
Metamaps.Router.timeoutId = undefined
|
||||
|
||||
Metamaps.Router.intercept = function (evt) {
|
||||
var segments
|
||||
|
||||
var href = {
|
||||
prop: $(this).prop('href'),
|
||||
attr: $(this).attr('href')
|
||||
}
|
||||
var root = window.location.protocol + '//' + window.location.host + Backbone.history.options.root
|
||||
|
||||
if (href.prop && href.prop === root) href.attr = ''
|
||||
|
||||
if (href.prop && href.prop.slice(0, root.length) === root) {
|
||||
evt.preventDefault()
|
||||
|
||||
segments = href.attr.split('/')
|
||||
segments.splice(0, 1) // pop off the element created by the first /
|
||||
|
||||
if (href.attr === '') {
|
||||
Metamaps.Router.home()
|
||||
} else {
|
||||
Metamaps.Router[segments[0]](segments[1], segments[2])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Metamaps.Router.init = function () {
|
||||
Backbone.history.start({
|
||||
silent: true,
|
||||
pushState: true,
|
||||
root: '/'
|
||||
})
|
||||
$(document).on('click', 'a[data-router="true"]', Metamaps.Router.intercept)
|
||||
}
|
||||
})()
|
|
@ -1,130 +0,0 @@
|
|||
/* global Metamaps */
|
||||
|
||||
/*
|
||||
* Metamaps.Util.js
|
||||
*
|
||||
* Dependencies:
|
||||
* - Metamaps.Visualize
|
||||
*/
|
||||
|
||||
Metamaps.Util = {
|
||||
// helper function to determine how many lines are needed
|
||||
// Line Splitter Function
|
||||
// copyright Stephen Chapman, 19th April 2006
|
||||
// you may copy this code but please keep the copyright notice as well
|
||||
splitLine: function (st, n) {
|
||||
var b = ''
|
||||
var s = st ? st : ''
|
||||
while (s.length > n) {
|
||||
var c = s.substring(0, n)
|
||||
var d = c.lastIndexOf(' ')
|
||||
var e = c.lastIndexOf('\n')
|
||||
if (e != -1) d = e
|
||||
if (d == -1) d = n
|
||||
b += c.substring(0, d) + '\n'
|
||||
s = s.substring(d + 1)
|
||||
}
|
||||
return b + s
|
||||
},
|
||||
nowDateFormatted: function () {
|
||||
var date = new Date(Date.now())
|
||||
var month = (date.getMonth() + 1) < 10 ? '0' + (date.getMonth() + 1) : (date.getMonth() + 1)
|
||||
var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate()
|
||||
var year = date.getFullYear()
|
||||
|
||||
return month + '/' + day + '/' + year
|
||||
},
|
||||
decodeEntities: function (desc) {
|
||||
var str, temp = document.createElement('p')
|
||||
temp.innerHTML = desc // browser handles the topics
|
||||
str = temp.textContent || temp.innerText
|
||||
temp = null // delete the element
|
||||
return str
|
||||
}, // decodeEntities
|
||||
getDistance: function (p1, p2) {
|
||||
return Math.sqrt(Math.pow((p2.x - p1.x), 2) + Math.pow((p2.y - p1.y), 2))
|
||||
},
|
||||
coordsToPixels: function (coords) {
|
||||
if (Metamaps.Visualize.mGraph) {
|
||||
var canvas = Metamaps.Visualize.mGraph.canvas,
|
||||
s = canvas.getSize(),
|
||||
p = canvas.getPos(),
|
||||
ox = canvas.translateOffsetX,
|
||||
oy = canvas.translateOffsetY,
|
||||
sx = canvas.scaleOffsetX,
|
||||
sy = canvas.scaleOffsetY
|
||||
var pixels = {
|
||||
x: (coords.x / (1 / sx)) + p.x + s.width / 2 + ox,
|
||||
y: (coords.y / (1 / sy)) + p.y + s.height / 2 + oy
|
||||
}
|
||||
return pixels
|
||||
} else {
|
||||
return {
|
||||
x: 0,
|
||||
y: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
pixelsToCoords: function (pixels) {
|
||||
var coords
|
||||
if (Metamaps.Visualize.mGraph) {
|
||||
var canvas = Metamaps.Visualize.mGraph.canvas,
|
||||
s = canvas.getSize(),
|
||||
p = canvas.getPos(),
|
||||
ox = canvas.translateOffsetX,
|
||||
oy = canvas.translateOffsetY,
|
||||
sx = canvas.scaleOffsetX,
|
||||
sy = canvas.scaleOffsetY
|
||||
coords = {
|
||||
x: (pixels.x - p.x - s.width / 2 - ox) * (1 / sx),
|
||||
y: (pixels.y - p.y - s.height / 2 - oy) * (1 / sy),
|
||||
}
|
||||
} else {
|
||||
coords = {
|
||||
x: 0,
|
||||
y: 0
|
||||
}
|
||||
}
|
||||
return coords
|
||||
},
|
||||
getPastelColor: function () {
|
||||
var r = (Math.round(Math.random() * 127) + 127).toString(16)
|
||||
var g = (Math.round(Math.random() * 127) + 127).toString(16)
|
||||
var b = (Math.round(Math.random() * 127) + 127).toString(16)
|
||||
return Metamaps.Util.colorLuminance('#' + r + g + b, -0.4)
|
||||
},
|
||||
// darkens a hex value by 'lum' percentage
|
||||
colorLuminance: function (hex, lum) {
|
||||
// validate hex string
|
||||
hex = String(hex).replace(/[^0-9a-f]/gi, '')
|
||||
if (hex.length < 6) {
|
||||
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]
|
||||
}
|
||||
lum = lum || 0
|
||||
|
||||
// convert to decimal and change luminosity
|
||||
var rgb = '#', c, i
|
||||
for (i = 0; i < 3; i++) {
|
||||
c = parseInt(hex.substr(i * 2, 2), 16)
|
||||
c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16)
|
||||
rgb += ('00' + c).substr(c.length)
|
||||
}
|
||||
|
||||
return rgb
|
||||
},
|
||||
generateOptionsList: function (data) {
|
||||
var newlist = ''
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
newlist = newlist + '<option value="' + data[i]['id'] + '">' + data[i]['1'][1] + '</option>'
|
||||
}
|
||||
return newlist
|
||||
},
|
||||
checkURLisImage: function (url) {
|
||||
// when the page reloads the following regular expression will be screwed up
|
||||
// please replace it with this one before you save: /*backslashhere*.(jpeg|jpg|gif|png)$/
|
||||
return (url.match(/\.(jpeg|jpg|gif|png)$/) != null)
|
||||
},
|
||||
checkURLisYoutubeVideo: function (url) {
|
||||
return (url.match(/^https?:\/\/(?:www\.)?youtube.com\/watch\?(?=[^?]*v=\w+)(?:[^\s?]+)?$/) != null)
|
||||
}
|
||||
}; // end Metamaps.Util
|
|
@ -1,87 +0,0 @@
|
|||
/* global Metamaps, $ */
|
||||
|
||||
/*
|
||||
* Metamaps.Views.js.erb
|
||||
*
|
||||
* Dependencies:
|
||||
* - Metamaps.Loading
|
||||
* - Metamaps.Active
|
||||
* - Metamaps.ReactComponents
|
||||
*/
|
||||
|
||||
Metamaps.Views = {
|
||||
exploreMaps: {
|
||||
setCollection: function (collection) {
|
||||
var self = Metamaps.Views.exploreMaps
|
||||
|
||||
if (self.collection) {
|
||||
self.collection.off('add', self.render)
|
||||
self.collection.off('successOnFetch', self.handleSuccess)
|
||||
self.collection.off('errorOnFetch', self.handleError)
|
||||
}
|
||||
self.collection = collection
|
||||
self.collection.on('add', self.render)
|
||||
self.collection.on('successOnFetch', self.handleSuccess)
|
||||
self.collection.on('errorOnFetch', self.handleError)
|
||||
},
|
||||
render: function (mapperObj, cb) {
|
||||
var self = Metamaps.Views.exploreMaps
|
||||
|
||||
if (typeof mapperObj === 'function') {
|
||||
cb = mapperObj
|
||||
mapperObj = null
|
||||
}
|
||||
|
||||
var exploreObj = {
|
||||
currentUser: Metamaps.Active.Mapper,
|
||||
section: self.collection.id,
|
||||
displayStyle: 'grid',
|
||||
maps: self.collection,
|
||||
moreToLoad: self.collection.page != 'loadedAll',
|
||||
user: mapperObj,
|
||||
loadMore: self.loadMore
|
||||
}
|
||||
ReactDOM.render(
|
||||
React.createElement(Metamaps.ReactComponents.Maps, exploreObj),
|
||||
document.getElementById('explore')
|
||||
)
|
||||
|
||||
if (cb) cb()
|
||||
Metamaps.Loading.hide()
|
||||
},
|
||||
loadMore: function () {
|
||||
var self = Metamaps.Views.exploreMaps
|
||||
|
||||
if (self.collection.page != "loadedAll") {
|
||||
self.collection.getMaps()
|
||||
}
|
||||
else self.render()
|
||||
},
|
||||
handleSuccess: function (cb) {
|
||||
var self = Metamaps.Views.exploreMaps
|
||||
|
||||
if (self.collection && self.collection.id === 'mapper') {
|
||||
self.fetchUserThenRender(cb)
|
||||
} else {
|
||||
self.render(cb)
|
||||
}
|
||||
},
|
||||
handleError: function () {
|
||||
console.log('error loading maps!') // TODO
|
||||
},
|
||||
fetchUserThenRender: function (cb) {
|
||||
var self = Metamaps.Views.exploreMaps
|
||||
|
||||
// first load the mapper object and then call the render function
|
||||
$.ajax({
|
||||
url: '/users/' + self.collection.mapperId + '/details.json',
|
||||
success: function (response) {
|
||||
self.render(response, cb)
|
||||
},
|
||||
error: function () {
|
||||
self.render(cb)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
/* global Metamaps */
|
||||
|
||||
/*
|
||||
* Metamaps.js.erb
|
||||
*/
|
||||
|
||||
// TODO eliminate these 5 top-level variables
|
||||
Metamaps.panningInt = null
|
||||
Metamaps.tempNode = null
|
||||
Metamaps.tempInit = false
|
||||
Metamaps.tempNode2 = null
|
||||
Metamaps.VERSION = '<%= METAMAPS_VERSION %>'
|
||||
Metamaps.LAST_UPDATED = '<%= METAMAPS_LAST_UPDATED %>'
|
||||
|
||||
/* erb variables from rails */
|
||||
Metamaps.Erb = {}
|
||||
Metamaps.Erb['REALTIME_SERVER'] = '<%= ENV['REALTIME_SERVER'] %>'
|
||||
Metamaps.Erb['junto_spinner_darkgrey.gif'] = '<%= asset_path('junto_spinner_darkgrey.gif') %>'
|
||||
Metamaps.Erb['user.png'] = '<%= asset_path('user.png') %>'
|
||||
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.Metacodes = <%= Metacode.all.to_json.gsub(%r[(icon.*?)(\"},)], '\1?purple=stupid\2').html_safe %>
|
||||
|
||||
Metamaps.Settings = {
|
||||
embed: false, // indicates that the app is on a page that is optimized for embedding in iFrames on other web pages
|
||||
sandbox: false, // puts the app into a mode (when true) where it only creates data locally, and isn't writing it to the database
|
||||
colors: {
|
||||
background: '#344A58',
|
||||
synapses: {
|
||||
normal: '#888888',
|
||||
hover: '#888888',
|
||||
selected: '#FFFFFF'
|
||||
},
|
||||
topics: {
|
||||
selected: '#FFFFFF'
|
||||
},
|
||||
labels: {
|
||||
background: '#18202E',
|
||||
text: '#DDD'
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Metamaps.Touch = {
|
||||
touchPos: null, // this stores the x and y values of a current touch event
|
||||
touchDragNode: null // this stores a reference to a JIT node that is being dragged
|
||||
}
|
||||
|
||||
Metamaps.Mouse = {
|
||||
didPan: false,
|
||||
didBoxZoom: false,
|
||||
changeInX: 0,
|
||||
changeInY: 0,
|
||||
edgeHoveringOver: false,
|
||||
boxStartCoordinates: false,
|
||||
boxEndCoordinates: false,
|
||||
synapseStartCoordinates: [],
|
||||
synapseEndCoordinates: null,
|
||||
lastNodeClick: 0,
|
||||
lastCanvasClick: 0,
|
||||
DOUBLE_CLICK_TOLERANCE: 300
|
||||
}
|
||||
|
||||
Metamaps.Selected = {
|
||||
reset: function () {
|
||||
var self = Metamaps.Selected
|
||||
|
||||
self.Nodes = []
|
||||
self.Edges = []
|
||||
},
|
||||
Nodes: [],
|
||||
Edges: []
|
||||
}
|
|
@ -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)
|
||||
})()
|
|
@ -1,343 +0,0 @@
|
|||
Metamaps.Views = Metamaps.Views || {};
|
||||
|
||||
Metamaps.Views.chatView = (function () {
|
||||
var
|
||||
chatView,
|
||||
linker = new Autolinker({ newWindow: true, truncate: 50, email: false, phone: false, twitter: false });
|
||||
|
||||
var Private = {
|
||||
messageHTML: "<div class='chat-message'>" +
|
||||
"<div class='chat-message-user'><img src='{{ user_image }}' title='{{user_name }}'/></div>" +
|
||||
"<div class='chat-message-text'>{{ message }}</div>" +
|
||||
"<div class='chat-message-time'>{{ timestamp }}</div>" +
|
||||
"<div class='clearfloat'></div>" +
|
||||
"</div>",
|
||||
participantHTML: "<div class='participant participant-{{ id }} {{ selfClass }}'>" +
|
||||
"<div class='chat-participant-image'><img src='{{ image }}' style='border: 2px solid {{ color }};' /></div>" +
|
||||
"<div class='chat-participant-name'>{{ username }} {{ selfName }}</div>" +
|
||||
"<button type='button' class='button chat-participant-invite-call' onclick='Metamaps.Realtime.inviteACall({{ id}});'></button>" +
|
||||
"<button type='button' class='button chat-participant-invite-join' onclick='Metamaps.Realtime.inviteToJoin({{ id}});'></button>" +
|
||||
"<span class='chat-participant-participating'><div class='green-dot'></div></span>" +
|
||||
"<div class='clearfloat'></div>" +
|
||||
"</div>",
|
||||
templates: function() {
|
||||
_.templateSettings = {
|
||||
interpolate: /\{\{(.+?)\}\}/g
|
||||
};
|
||||
this.messageTemplate = _.template(Private.messageHTML);
|
||||
|
||||
this.participantTemplate = _.template(Private.participantHTML);
|
||||
},
|
||||
createElements: function() {
|
||||
this.$unread = $('<div class="chat-unread"></div>');
|
||||
this.$button = $('<div class="chat-button"><div class="tooltips">Chat</div></div>');
|
||||
this.$messageInput = $('<textarea placeholder="Send a message..." class="chat-input"></textarea>');
|
||||
this.$juntoHeader = $('<div class="junto-header">PARTICIPANTS</div>');
|
||||
this.$videoToggle = $('<div class="video-toggle"></div>');
|
||||
this.$cursorToggle = $('<div class="cursor-toggle"></div>');
|
||||
this.$participants = $('<div class="participants"></div>');
|
||||
this.$conversationInProgress = $('<div class="conversation-live">LIVE <span class="call-action leave" onclick="Metamaps.Realtime.leaveCall();">LEAVE</span><span class="call-action join" onclick="Metamaps.Realtime.joinCall();">JOIN</span></div>');
|
||||
this.$chatHeader = $('<div class="chat-header">CHAT</div>');
|
||||
this.$soundToggle = $('<div class="sound-toggle"></div>');
|
||||
this.$messages = $('<div class="chat-messages"></div>');
|
||||
this.$container = $('<div class="chat-box"></div>');
|
||||
},
|
||||
attachElements: function() {
|
||||
this.$button.append(this.$unread);
|
||||
|
||||
this.$juntoHeader.append(this.$videoToggle);
|
||||
this.$juntoHeader.append(this.$cursorToggle);
|
||||
|
||||
this.$chatHeader.append(this.$soundToggle);
|
||||
|
||||
this.$participants.append(this.$conversationInProgress);
|
||||
|
||||
this.$container.append(this.$juntoHeader);
|
||||
this.$container.append(this.$participants);
|
||||
this.$container.append(this.$chatHeader);
|
||||
this.$container.append(this.$button);
|
||||
this.$container.append(this.$messages);
|
||||
this.$container.append(this.$messageInput);
|
||||
},
|
||||
addEventListeners: function() {
|
||||
var self = this;
|
||||
|
||||
this.participants.on('add', function (participant) {
|
||||
Private.addParticipant.call(self, participant);
|
||||
});
|
||||
|
||||
this.participants.on('remove', function (participant) {
|
||||
Private.removeParticipant.call(self, participant);
|
||||
});
|
||||
|
||||
this.$button.on('click', function () {
|
||||
Handlers.buttonClick.call(self);
|
||||
});
|
||||
this.$videoToggle.on('click', function () {
|
||||
Handlers.videoToggleClick.call(self);
|
||||
});
|
||||
this.$cursorToggle.on('click', function () {
|
||||
Handlers.cursorToggleClick.call(self);
|
||||
});
|
||||
this.$soundToggle.on('click', function () {
|
||||
Handlers.soundToggleClick.call(self);
|
||||
});
|
||||
this.$messageInput.on('keyup', function (event) {
|
||||
Handlers.keyUp.call(self, event);
|
||||
});
|
||||
this.$messageInput.on('focus', function () {
|
||||
Handlers.inputFocus.call(self);
|
||||
});
|
||||
this.$messageInput.on('blur', function () {
|
||||
Handlers.inputBlur.call(self);
|
||||
});
|
||||
},
|
||||
initializeSounds: function() {
|
||||
this.sound = new Howl({
|
||||
urls: ["<%= asset_path 'sounds/MM_sounds.mp3' %>", "<%= asset_path 'sounds/MM_sounds.ogg' %>"],
|
||||
sprite: {
|
||||
joinmap: [0, 561],
|
||||
leavemap: [1000, 592],
|
||||
receivechat: [2000, 318],
|
||||
sendchat: [3000, 296],
|
||||
sessioninvite: [4000, 5393, true]
|
||||
}
|
||||
});
|
||||
},
|
||||
incrementUnread: function() {
|
||||
this.unreadMessages++;
|
||||
this.$unread.html(this.unreadMessages);
|
||||
this.$unread.show();
|
||||
},
|
||||
addMessage: function(message, isInitial, wasMe) {
|
||||
|
||||
if (!this.isOpen && !isInitial) Private.incrementUnread.call(this);
|
||||
|
||||
function addZero(i) {
|
||||
if (i < 10) {
|
||||
i = "0" + i;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
var m = _.clone(message.attributes);
|
||||
|
||||
var today = new Date();
|
||||
m.timestamp = new Date(m.created_at);
|
||||
|
||||
var date = (m.timestamp.getMonth() + 1) + '/' + m.timestamp.getDate();
|
||||
date += " " + addZero(m.timestamp.getHours()) + ":" + addZero(m.timestamp.getMinutes());
|
||||
m.timestamp = date;
|
||||
m.image = m.user_image || 'http://www.hotpepper.ca/wp-content/uploads/2014/11/default_profile_1_200x200.png'; // TODO: remove
|
||||
m.message = linker.link(m.message);
|
||||
var $html = $(this.messageTemplate(m));
|
||||
this.$messages.append($html);
|
||||
if (!isInitial) this.scrollMessages(200);
|
||||
|
||||
if (!wasMe && !isInitial && this.alertSound) this.sound.play('receivechat');
|
||||
},
|
||||
initialMessages: function() {
|
||||
var messages = this.messages.models;
|
||||
for (var i = 0; i < messages.length; i++) {
|
||||
Private.addMessage.call(this, messages[i], true);
|
||||
}
|
||||
},
|
||||
handleInputMessage: function() {
|
||||
var message = {
|
||||
message: this.$messageInput.val(),
|
||||
};
|
||||
this.$messageInput.val('');
|
||||
$(document).trigger(chatView.events.message + '-' + this.room, [message]);
|
||||
},
|
||||
addParticipant: function(participant) {
|
||||
var p = _.clone(participant.attributes);
|
||||
if (p.self) {
|
||||
p.selfClass = 'is-self';
|
||||
p.selfName = '(me)';
|
||||
} else {
|
||||
p.selfClass = '';
|
||||
p.selfName = '';
|
||||
}
|
||||
var html = this.participantTemplate(p);
|
||||
this.$participants.append(html);
|
||||
},
|
||||
removeParticipant: function(participant) {
|
||||
this.$container.find('.participant-' + participant.get('id')).remove();
|
||||
}
|
||||
};
|
||||
|
||||
var Handlers = {
|
||||
buttonClick: function() {
|
||||
if (this.isOpen) this.close();
|
||||
else if (!this.isOpen) this.open();
|
||||
},
|
||||
videoToggleClick: function() {
|
||||
this.$videoToggle.toggleClass('active');
|
||||
this.videosShowing = !this.videosShowing;
|
||||
$(document).trigger(this.videosShowing ? chatView.events.videosOn : chatView.events.videosOff);
|
||||
},
|
||||
cursorToggleClick: function() {
|
||||
this.$cursorToggle.toggleClass('active');
|
||||
this.cursorsShowing = !this.cursorsShowing;
|
||||
$(document).trigger(this.cursorsShowing ? chatView.events.cursorsOn : chatView.events.cursorsOff);
|
||||
},
|
||||
soundToggleClick: function() {
|
||||
this.alertSound = !this.alertSound;
|
||||
this.$soundToggle.toggleClass('active');
|
||||
},
|
||||
keyUp: function(event) {
|
||||
switch(event.which) {
|
||||
case 13: // enter
|
||||
Private.handleInputMessage.call(this);
|
||||
break;
|
||||
}
|
||||
},
|
||||
inputFocus: function() {
|
||||
$(document).trigger(chatView.events.inputFocus);
|
||||
},
|
||||
inputBlur: function() {
|
||||
$(document).trigger(chatView.events.inputBlur);
|
||||
}
|
||||
};
|
||||
|
||||
chatView = function(messages, mapper, room) {
|
||||
var self = this;
|
||||
|
||||
this.room = room;
|
||||
this.mapper = mapper;
|
||||
this.messages = messages; // backbone collection
|
||||
|
||||
this.isOpen = false;
|
||||
this.alertSound = true; // whether to play sounds on arrival of new messages or not
|
||||
this.cursorsShowing = true;
|
||||
this.videosShowing = true;
|
||||
this.unreadMessages = 0;
|
||||
this.participants = new Backbone.Collection();
|
||||
|
||||
Private.templates.call(this);
|
||||
Private.createElements.call(this);
|
||||
Private.attachElements.call(this);
|
||||
Private.addEventListeners.call(this);
|
||||
Private.initialMessages.call(this);
|
||||
Private.initializeSounds.call(this);
|
||||
this.$container.css({
|
||||
right: '-300px'
|
||||
});
|
||||
};
|
||||
|
||||
chatView.prototype.conversationInProgress = function (participating) {
|
||||
this.$conversationInProgress.show();
|
||||
this.$participants.addClass('is-live');
|
||||
if (participating) this.$participants.addClass('is-participating');
|
||||
this.$button.addClass('active');
|
||||
|
||||
// hide invite to call buttons
|
||||
}
|
||||
|
||||
chatView.prototype.conversationEnded = function () {
|
||||
this.$conversationInProgress.hide();
|
||||
this.$participants.removeClass('is-live');
|
||||
this.$participants.removeClass('is-participating');
|
||||
this.$button.removeClass('active');
|
||||
this.$participants.find('.participant').removeClass('active');
|
||||
this.$participants.find('.participant').removeClass('pending');
|
||||
}
|
||||
|
||||
chatView.prototype.leaveConversation = function () {
|
||||
this.$participants.removeClass('is-participating');
|
||||
}
|
||||
|
||||
chatView.prototype.mapperJoinedCall = function (id) {
|
||||
this.$participants.find('.participant-' + id).addClass('active');
|
||||
}
|
||||
|
||||
chatView.prototype.mapperLeftCall = function (id) {
|
||||
this.$participants.find('.participant-' + id).removeClass('active');
|
||||
}
|
||||
|
||||
chatView.prototype.invitationPending = function (id) {
|
||||
this.$participants.find('.participant-' + id).addClass('pending');
|
||||
}
|
||||
|
||||
chatView.prototype.invitationAnswered = function (id) {
|
||||
this.$participants.find('.participant-' + id).removeClass('pending');
|
||||
}
|
||||
|
||||
chatView.prototype.addParticipant = function (participant) {
|
||||
this.participants.add(participant);
|
||||
}
|
||||
|
||||
chatView.prototype.removeParticipant = function (username) {
|
||||
var p = this.participants.find(function (p) { return p.get('username') === username; });
|
||||
if (p) {
|
||||
this.participants.remove(p);
|
||||
}
|
||||
}
|
||||
|
||||
chatView.prototype.removeParticipants = function () {
|
||||
this.participants.remove(this.participants.models);
|
||||
}
|
||||
|
||||
chatView.prototype.open = function () {
|
||||
this.$container.css({
|
||||
right: '0'
|
||||
});
|
||||
this.$messageInput.focus();
|
||||
this.isOpen = true;
|
||||
this.unreadMessages = 0;
|
||||
this.$unread.hide();
|
||||
this.scrollMessages(0);
|
||||
$(document).trigger(chatView.events.openTray);
|
||||
}
|
||||
|
||||
chatView.prototype.addMessage = function(message, isInitial, wasMe) {
|
||||
this.messages.add(message);
|
||||
Private.addMessage.call(this, message, isInitial, wasMe);
|
||||
}
|
||||
|
||||
chatView.prototype.scrollMessages = function(duration) {
|
||||
duration = duration || 0;
|
||||
|
||||
this.$messages.animate({
|
||||
scrollTop: this.$messages[0].scrollHeight
|
||||
}, duration);
|
||||
}
|
||||
|
||||
chatView.prototype.clearMessages = function () {
|
||||
this.unreadMessages = 0;
|
||||
this.$unread.hide();
|
||||
this.$messages.empty();
|
||||
}
|
||||
|
||||
chatView.prototype.close = function () {
|
||||
this.$container.css({
|
||||
right: '-300px'
|
||||
});
|
||||
this.$messageInput.blur();
|
||||
this.isOpen = false;
|
||||
$(document).trigger(chatView.events.closeTray);
|
||||
}
|
||||
|
||||
chatView.prototype.remove = function () {
|
||||
this.$button.off();
|
||||
this.$container.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* @class
|
||||
* @static
|
||||
*/
|
||||
chatView.events = {
|
||||
message: 'ChatView:message',
|
||||
openTray: 'ChatView:openTray',
|
||||
closeTray: 'ChatView:closeTray',
|
||||
inputFocus: 'ChatView:inputFocus',
|
||||
inputBlur: 'ChatView:inputBlur',
|
||||
cursorsOff: 'ChatView:cursorsOff',
|
||||
cursorsOn: 'ChatView:cursorsOn',
|
||||
videosOff: 'ChatView:videosOff',
|
||||
videosOn: 'ChatView:videosOn'
|
||||
};
|
||||
|
||||
return chatView;
|
||||
|
||||
})();
|
|
@ -1,195 +0,0 @@
|
|||
Metamaps.Views = Metamaps.Views || {};
|
||||
|
||||
Metamaps.Views.room = (function () {
|
||||
|
||||
var ChatView = Metamaps.Views.chatView;
|
||||
var VideoView = Metamaps.Views.videoView;
|
||||
|
||||
var room = function(opts) {
|
||||
var self = this;
|
||||
|
||||
this.isActiveRoom = false;
|
||||
this.socket = opts.socket;
|
||||
this.webrtc = opts.webrtc;
|
||||
//this.roomRef = opts.firebase;
|
||||
this.room = opts.room;
|
||||
this.config = opts.config;
|
||||
this.peopleCount = 0;
|
||||
|
||||
this.$myVideo = opts.$video;
|
||||
this.myVideo = opts.myVideoView;
|
||||
|
||||
this.messages = new Backbone.Collection();
|
||||
this.currentMapper = new Backbone.Model({ name: opts.username, image: opts.image });
|
||||
this.chat = new ChatView(this.messages, this.currentMapper, this.room);
|
||||
|
||||
this.videos = {};
|
||||
|
||||
this.init();
|
||||
};
|
||||
|
||||
room.prototype.join = function(cb) {
|
||||
this.isActiveRoom = true;
|
||||
this.webrtc.joinRoom(this.room, cb);
|
||||
this.chat.conversationInProgress(true); // true indicates participation
|
||||
}
|
||||
|
||||
room.prototype.conversationInProgress = function() {
|
||||
this.chat.conversationInProgress(false); // false indicates not participating
|
||||
}
|
||||
|
||||
room.prototype.conversationEnding = function() {
|
||||
this.chat.conversationEnded();
|
||||
}
|
||||
|
||||
room.prototype.leaveVideoOnly = function() {
|
||||
this.chat.leaveConversation(); // the conversation will carry on without you
|
||||
for (var id in this.videos) {
|
||||
this.removeVideo(id);
|
||||
}
|
||||
this.isActiveRoom = false;
|
||||
this.webrtc.leaveRoom();
|
||||
}
|
||||
|
||||
room.prototype.leave = function() {
|
||||
for (var id in this.videos) {
|
||||
this.removeVideo(id);
|
||||
}
|
||||
this.isActiveRoom = false;
|
||||
this.webrtc.leaveRoom();
|
||||
this.chat.conversationEnded();
|
||||
this.chat.removeParticipants();
|
||||
this.chat.clearMessages();
|
||||
this.messages.reset();
|
||||
}
|
||||
|
||||
room.prototype.setPeopleCount = function(count) {
|
||||
this.peopleCount = count;
|
||||
}
|
||||
|
||||
room.prototype.init = function () {
|
||||
var self = this;
|
||||
|
||||
$(document).on(VideoView.events.audioControlClick, function (event, videoView) {
|
||||
if (!videoView.audioStatus) self.webrtc.mute();
|
||||
else if (videoView.audioStatus) self.webrtc.unmute();
|
||||
});
|
||||
$(document).on(VideoView.events.videoControlClick, function (event, videoView) {
|
||||
if (!videoView.videoStatus) self.webrtc.pauseVideo();
|
||||
else if (videoView.videoStatus) self.webrtc.resumeVideo();
|
||||
});
|
||||
|
||||
this.webrtc.webrtc.off('peerStreamAdded');
|
||||
this.webrtc.webrtc.off('peerStreamRemoved');
|
||||
this.webrtc.on('peerStreamAdded', function (peer) {
|
||||
var mapper = Metamaps.Realtime.mappersOnMap[peer.nick];
|
||||
peer.avatar = mapper.image;
|
||||
peer.username = mapper.name;
|
||||
if (self.isActiveRoom) {
|
||||
self.addVideo(peer);
|
||||
}
|
||||
});
|
||||
|
||||
this.webrtc.on('peerStreamRemoved', function (peer) {
|
||||
if (self.isActiveRoom) {
|
||||
self.removeVideo(peer);
|
||||
}
|
||||
});
|
||||
|
||||
this.webrtc.on('mute', function (data) {
|
||||
var v = self.videos[data.id];
|
||||
if (!v) return;
|
||||
|
||||
if (data.name === 'audio') {
|
||||
v.audioStatus = false;
|
||||
}
|
||||
else if (data.name === 'video') {
|
||||
v.videoStatus = false;
|
||||
v.$avatar.show();
|
||||
}
|
||||
if (!v.audioStatus && !v.videoStatus) v.$container.hide();
|
||||
});
|
||||
this.webrtc.on('unmute', function (data) {
|
||||
var v = self.videos[data.id];
|
||||
if (!v) return;
|
||||
|
||||
if (data.name === 'audio') {
|
||||
v.audioStatus = true;
|
||||
}
|
||||
else if (data.name === 'video') {
|
||||
v.videoStatus = true;
|
||||
v.$avatar.hide();
|
||||
}
|
||||
v.$container.show();
|
||||
});
|
||||
|
||||
var sendChatMessage = function (event, data) {
|
||||
self.sendChatMessage(data);
|
||||
};
|
||||
$(document).on(ChatView.events.message + '-' + this.room, sendChatMessage);
|
||||
}
|
||||
|
||||
room.prototype.videoAdded = function (callback) {
|
||||
this._videoAdded = callback;
|
||||
}
|
||||
|
||||
room.prototype.addVideo = function (peer) {
|
||||
var
|
||||
id = this.webrtc.getDomId(peer),
|
||||
video = attachMediaStream(peer.stream);
|
||||
|
||||
var
|
||||
v = new VideoView(video, null, id, false, { DOUBLE_CLICK_TOLERANCE: 200, avatar: peer.avatar, username: peer.username });
|
||||
|
||||
this.videos[peer.id] = v;
|
||||
if (this._videoAdded) this._videoAdded(v, peer.nick);
|
||||
}
|
||||
|
||||
room.prototype.removeVideo = function (peer) {
|
||||
var id = typeof peer == 'string' ? peer : peer.id;
|
||||
if (this.videos[id]) {
|
||||
this.videos[id].remove();
|
||||
delete this.videos[id];
|
||||
}
|
||||
}
|
||||
|
||||
room.prototype.sendChatMessage = function (data) {
|
||||
var self = this;
|
||||
//this.roomRef.child('messages').push(data);
|
||||
if (self.chat.alertSound) self.chat.sound.play('sendchat');
|
||||
var m = new Metamaps.Backbone.Message({
|
||||
message: data.message,
|
||||
resource_id: Metamaps.Active.Map.id,
|
||||
resource_type: "Map"
|
||||
});
|
||||
m.save(null, {
|
||||
success: function (model, response) {
|
||||
self.addMessages(new Metamaps.Backbone.MessageCollection(model), false, true);
|
||||
$(document).trigger(room.events.newMessage, [model]);
|
||||
},
|
||||
error: function (model, response) {
|
||||
console.log('error!', response);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// they should be instantiated as backbone models before they get
|
||||
// passed to this function
|
||||
room.prototype.addMessages = function (messages, isInitial, wasMe) {
|
||||
var self = this;
|
||||
|
||||
messages.models.forEach(function (message) {
|
||||
self.chat.addMessage(message, isInitial, wasMe);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @class
|
||||
* @static
|
||||
*/
|
||||
room.events = {
|
||||
newMessage: "Room:newMessage"
|
||||
};
|
||||
|
||||
return room;
|
||||
})();
|
|
@ -1,207 +0,0 @@
|
|||
Metamaps.Views = Metamaps.Views || {};
|
||||
|
||||
Metamaps.Views.videoView = (function () {
|
||||
|
||||
var videoView;
|
||||
|
||||
var Private = {
|
||||
addControls: function() {
|
||||
var self = this;
|
||||
|
||||
this.$audioControl = $('<div class="video-audio"></div>');
|
||||
this.$videoControl = $('<div class="video-video"></div>');
|
||||
|
||||
this.$audioControl.on('click', function () {
|
||||
Handlers.audioControlClick.call(self);
|
||||
});
|
||||
|
||||
this.$videoControl.on('click', function () {
|
||||
Handlers.videoControlClick.call(self);
|
||||
});
|
||||
|
||||
this.$container.append(this.$audioControl);
|
||||
this.$container.append(this.$videoControl);
|
||||
},
|
||||
cancelClick: function() {
|
||||
this.mouseIsDown = false;
|
||||
|
||||
if (this.hasMoved) {
|
||||
|
||||
}
|
||||
|
||||
$(document).trigger(videoView.events.dragEnd);
|
||||
}
|
||||
};
|
||||
|
||||
var Handlers = {
|
||||
mousedown: function(event) {
|
||||
this.mouseIsDown = true;
|
||||
this.hasMoved = false;
|
||||
this.mouseMoveStart = {
|
||||
x: event.pageX,
|
||||
y: event.pageY
|
||||
};
|
||||
this.posStart = {
|
||||
x: parseInt(this.$container.css('left'), '10'),
|
||||
y: parseInt(this.$container.css('top'), '10')
|
||||
}
|
||||
|
||||
$(document).trigger(videoView.events.mousedown);
|
||||
},
|
||||
mouseup: function(event) {
|
||||
$(document).trigger(videoView.events.mouseup, [this]);
|
||||
|
||||
var storedTime = this.lastClick;
|
||||
var now = Date.now();
|
||||
this.lastClick = now;
|
||||
|
||||
if (now - storedTime < this.config.DOUBLE_CLICK_TOLERANCE) {
|
||||
$(document).trigger(videoView.events.doubleClick, [this]);
|
||||
}
|
||||
},
|
||||
mousemove: function(event) {
|
||||
var
|
||||
diffX,
|
||||
diffY,
|
||||
newX,
|
||||
newY;
|
||||
|
||||
if (this.$parent && this.mouseIsDown) {
|
||||
this.manuallyPositioned = true;
|
||||
this.hasMoved = true;
|
||||
diffX = event.pageX - this.mouseMoveStart.x;
|
||||
diffY = this.mouseMoveStart.y - event.pageY;
|
||||
newX = this.posStart.x + diffX;
|
||||
newY = this.posStart.y - diffY;
|
||||
this.$container.css({
|
||||
top: newY,
|
||||
left: newX
|
||||
});
|
||||
}
|
||||
},
|
||||
audioControlClick: function() {
|
||||
if (this.audioStatus) {
|
||||
this.audioOff();
|
||||
} else {
|
||||
this.audioOn();
|
||||
}
|
||||
$(document).trigger(videoView.events.audioControlClick, [this]);
|
||||
},
|
||||
videoControlClick: function() {
|
||||
if (this.videoStatus) {
|
||||
this.videoOff();
|
||||
} else {
|
||||
this.videoOn();
|
||||
}
|
||||
$(document).trigger(videoView.events.videoControlClick, [this]);
|
||||
},
|
||||
};
|
||||
|
||||
var videoView = function(video, $parent, id, isMyself, config) {
|
||||
var self = this;
|
||||
|
||||
this.$parent = $parent; // mapView
|
||||
|
||||
this.video = video;
|
||||
this.id = id;
|
||||
|
||||
this.config = config;
|
||||
|
||||
this.mouseIsDown = false;
|
||||
this.mouseDownOffset = { x: 0, y: 0 };
|
||||
this.lastClick = null;
|
||||
this.hasMoved = false;
|
||||
|
||||
this.audioStatus = true;
|
||||
this.videoStatus = true;
|
||||
|
||||
this.$container = $('<div></div>');
|
||||
this.$container.addClass('collaborator-video' + (isMyself ? ' my-video' : ''));
|
||||
this.$container.attr('id', 'container_' + id);
|
||||
|
||||
|
||||
var $vidContainer = $('<div></div>');
|
||||
$vidContainer.addClass('video-cutoff');
|
||||
$vidContainer.append(this.video);
|
||||
|
||||
this.avatar = config.avatar;
|
||||
this.$avatar = $('<img draggable="false" class="collaborator-video-avatar" src="' + config.avatar + '" width="150" height="150" />');
|
||||
$vidContainer.append(this.$avatar);
|
||||
|
||||
this.$container.append($vidContainer);
|
||||
|
||||
this.$container.on('mousedown', function (event) {
|
||||
Handlers.mousedown.call(self, event);
|
||||
});
|
||||
|
||||
if (isMyself) {
|
||||
Private.addControls.call(this);
|
||||
}
|
||||
|
||||
// suppress contextmenu
|
||||
this.video.oncontextmenu = function () { return false; };
|
||||
|
||||
if (this.$parent) this.setParent(this.$parent);
|
||||
};
|
||||
|
||||
videoView.prototype.setParent = function($parent) {
|
||||
var self = this;
|
||||
this.$parent = $parent;
|
||||
this.$parent.off('.video' + this.id);
|
||||
this.$parent.on('mouseup.video' + this.id, function (event) {
|
||||
Handlers.mouseup.call(self, event);
|
||||
Private.cancelClick.call(self);
|
||||
});
|
||||
this.$parent.on('mousemove.video' + this.id, function (event) {
|
||||
Handlers.mousemove.call(self, event);
|
||||
});
|
||||
}
|
||||
|
||||
videoView.prototype.setAvatar = function (src) {
|
||||
this.$avatar.attr('src', src);
|
||||
this.avatar = src;
|
||||
}
|
||||
|
||||
videoView.prototype.remove = function () {
|
||||
this.$container.off();
|
||||
if (this.$parent) this.$parent.off('.video' + this.id);
|
||||
this.$container.remove();
|
||||
}
|
||||
|
||||
videoView.prototype.videoOff = function () {
|
||||
this.$videoControl.addClass('active');
|
||||
this.$avatar.show();
|
||||
this.videoStatus = false;
|
||||
}
|
||||
|
||||
videoView.prototype.videoOn = function () {
|
||||
this.$videoControl.removeClass('active');
|
||||
this.$avatar.hide();
|
||||
this.videoStatus = true;
|
||||
}
|
||||
|
||||
videoView.prototype.audioOff = function () {
|
||||
this.$audioControl.addClass('active');
|
||||
this.audioStatus = false;
|
||||
}
|
||||
|
||||
videoView.prototype.audioOn = function () {
|
||||
this.$audioControl.removeClass('active');
|
||||
this.audioStatus = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @class
|
||||
* @static
|
||||
*/
|
||||
videoView.events = {
|
||||
mousedown: "VideoView:mousedown",
|
||||
mouseup: "VideoView:mouseup",
|
||||
doubleClick: "VideoView:doubleClick",
|
||||
dragEnd: "VideoView:dragEnd",
|
||||
audioControlClick: "VideoView:audioControlClick",
|
||||
videoControlClick: "VideoView:videoControlClick",
|
||||
};
|
||||
|
||||
return videoView;
|
||||
})();
|
|
@ -78,11 +78,18 @@ html {
|
|||
|
||||
}
|
||||
body {
|
||||
background: #d8d9da url(<%= asset_data_uri('shattered_@2X.png') %>);
|
||||
font-family: 'din-medium', helvetica, sans-serif;
|
||||
color: #424242;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
overflow-x: hidden;
|
||||
background: #d8d9da url(<%= asset_path('shattered_@2X.png') %>);
|
||||
font-family: 'din-medium', helvetica, sans-serif;
|
||||
color: #424242;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
overflow-x: hidden;
|
||||
|
||||
&.controller-main,
|
||||
&.controller-maps,
|
||||
&.controller-topics,
|
||||
&.controller-explore {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
}
|
||||
h1,
|
||||
h2,
|
||||
|
@ -142,6 +149,7 @@ button.button.btn-no:hover {
|
|||
.toast .toast-button {
|
||||
margin-top: -10px;
|
||||
margin-left: 10px;
|
||||
margin-bottom: -10px;
|
||||
}
|
||||
/*
|
||||
* Utility
|
||||
|
@ -567,6 +575,26 @@ button.button.btn-no:hover {
|
|||
.openMetacodeSwitcher:hover {
|
||||
background-position: -16px 0;
|
||||
}
|
||||
.pinCarousel {
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
background-image: url(<%= asset_data_uri('pincarousel_sprite.png') %>);
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
top: 20px;
|
||||
right: 16px;
|
||||
}
|
||||
.pinCarousel:hover {
|
||||
background-position: 0 -16px;
|
||||
}
|
||||
.pinCarousel.isPinned {
|
||||
background-position: -16px 0;
|
||||
}
|
||||
.pinCarousel.isPinned:hover {
|
||||
background-position: -16px -16px;
|
||||
}
|
||||
#metacodeImg {
|
||||
height: 120px;
|
||||
}
|
||||
|
@ -806,6 +834,9 @@ label {
|
|||
.accountAdmin .accountIcon {
|
||||
background-position: 0 -32px;
|
||||
}
|
||||
.accountApps .accountIcon {
|
||||
background-position: 0 -32px;
|
||||
}
|
||||
.accountInvite .accountIcon {
|
||||
background-position: 0 -64px;
|
||||
}
|
||||
|
@ -1505,9 +1536,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;
|
||||
|
@ -1515,12 +1545,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;
|
||||
|
@ -2007,17 +2065,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;
|
||||
}
|
||||
|
@ -2056,8 +2114,10 @@ and it won't be important on password protected instances */
|
|||
background-position: center center;
|
||||
}
|
||||
#lightbox_content {
|
||||
width: 552px;
|
||||
height: 434px;
|
||||
width: 800px;
|
||||
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);
|
||||
|
@ -2139,41 +2199,34 @@ and it won't be important on password protected instances */
|
|||
color: #00bcd4;
|
||||
}
|
||||
.lightbox_links .lightboxAboutIcon {
|
||||
background-image: url(<%= asset_data_uri('about_sprite.png') %>);
|
||||
background-repeat: no-repeat;
|
||||
width:32px;
|
||||
height:32px;
|
||||
margin:10px auto;
|
||||
}
|
||||
#lightbox_metamapps .lightboxAboutIcon {
|
||||
.icon_twitter .lightboxAboutIcon,
|
||||
.icon_source_code .lightboxAboutIcon,
|
||||
.icon_terms .lightboxAboutIcon {
|
||||
background-image: url(<%= asset_data_uri('about_sprite.png') %>);
|
||||
background-repeat: no-repeat;
|
||||
background-position: 0 0;
|
||||
}
|
||||
#lightbox_community .lightboxAboutIcon {
|
||||
background-position: -32px 0;
|
||||
}
|
||||
#lightbox_source .lightboxAboutIcon {
|
||||
background-position: -64px 0;
|
||||
}
|
||||
#lightbox_blog .lightboxAboutIcon {
|
||||
background-position: -96px 0;
|
||||
}
|
||||
#lightbox_term .lightboxAboutIcon {
|
||||
background-position: -128px 0;
|
||||
}
|
||||
#lightbox_metamapps:hover .lightboxAboutIcon {
|
||||
.icon_twitter .lightboxAboutIcon {
|
||||
background-position: 0 0;
|
||||
&:hover {
|
||||
background-position: 0 -32px;
|
||||
}
|
||||
}
|
||||
#lightbox_community:hover .lightboxAboutIcon {
|
||||
background-position: -32px -32px;
|
||||
}
|
||||
#lightbox_source:hover .lightboxAboutIcon {
|
||||
.icon_source_code .lightboxAboutIcon {
|
||||
background-position: -64px 0;
|
||||
&:hover {
|
||||
background-position: -64px -32px;
|
||||
}
|
||||
}
|
||||
#lightbox_blog:hover .lightboxAboutIcon {
|
||||
background-position: -96px -32px;
|
||||
}
|
||||
#lightbox_term:hover .lightboxAboutIcon {
|
||||
.icon_terms .lightboxAboutIcon {
|
||||
background-position: -128px 0;
|
||||
&:hover {
|
||||
background-position: -128px -32px;
|
||||
}
|
||||
}
|
||||
|
||||
/* jquery ui tabs */
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
}
|
||||
|
||||
|
||||
#center-container {
|
||||
position:relative;
|
||||
height:100%;
|
||||
|
@ -70,6 +69,7 @@
|
|||
}
|
||||
|
||||
.CardOnGraph .title {
|
||||
word-break: break-word;
|
||||
font-size: 18px;
|
||||
line-height: 22px;
|
||||
display: table;
|
||||
|
@ -138,11 +138,35 @@
|
|||
resize: none;
|
||||
}
|
||||
|
||||
.CardOnGraph .desc h3 {
|
||||
font-style:normal;
|
||||
margin-top:5px;
|
||||
/*
|
||||
* Styling for Markdown in topic cards
|
||||
*/
|
||||
|
||||
.CardOnGraph .desc {
|
||||
p, ol, ul {
|
||||
padding: 0.15em 0;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-style: normal;
|
||||
padding: 0.25em 0;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul {
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* End Markdown styling
|
||||
*/
|
||||
|
||||
.CardOnGraph .best_in_place_desc {
|
||||
display:block;
|
||||
margin-top:2px;
|
||||
|
@ -582,10 +606,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,160 +966,14 @@ 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 */
|
||||
|
||||
.mapper {
|
||||
float: left;
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
width:220px;
|
||||
height:340px;
|
||||
font-size: 12px;
|
||||
|
@ -1103,7 +981,7 @@ font-family: 'din-regular', helvetica, sans-serif;
|
|||
overflow: visible;
|
||||
background: #E0E0E0;
|
||||
border-radius:2px;
|
||||
margin:16px 16px 16px 19px;
|
||||
margin:16px;
|
||||
box-shadow: 0px 3px 3px rgba(0,0,0,0.23), 0 3px 3px rgba(0,0,0,0.16);
|
||||
}
|
||||
|
||||
|
@ -1127,10 +1005,10 @@ font-family: 'din-regular', helvetica, sans-serif;
|
|||
font-size: 24px;
|
||||
text-align: center;
|
||||
margin-top: 24px;
|
||||
padding: 0 16px;
|
||||
padding: 0 5%;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
width: 189px;
|
||||
width: 90%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
|
@ -47,11 +47,14 @@
|
|||
.mapElement {
|
||||
display: none;
|
||||
}
|
||||
.mapPage .mapElement, .topicPage .mapElement {
|
||||
.mapPage .mapElement,
|
||||
.topicPage .mapElement {
|
||||
display: block;
|
||||
}
|
||||
.mapPage .mapElementHidden {
|
||||
display:none;
|
||||
.mapPage .mapElementHidden,
|
||||
.topicPage .mapElement.mapInfoBox,
|
||||
.topicPage .mapElement.importDialog {
|
||||
display:none;
|
||||
}
|
||||
.topicPage .starMap {
|
||||
display: none;
|
||||
|
@ -188,28 +191,38 @@
|
|||
.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;
|
||||
}
|
||||
.mapPage .mapElement .importDialog {
|
||||
display: none;
|
||||
background-position: 0 0;
|
||||
}
|
||||
.mapPage.canEditMap .mapElement .importDialog {
|
||||
display: block;
|
||||
}
|
||||
.sidebarFilterIcon {
|
||||
background-position: -64px 0;
|
||||
background-position: -32px 0;
|
||||
}
|
||||
.sidebarForkIcon {
|
||||
background-position: -96px 0;
|
||||
background-position: -64px 0;
|
||||
}
|
||||
.addMap {
|
||||
background-position: -128px 0;
|
||||
background-position: -96px 0;
|
||||
margin-right:10px;
|
||||
}
|
||||
.importDialog:hover {
|
||||
background-position: 0 -32px;
|
||||
}
|
||||
.sidebarFilterIcon:hover {
|
||||
background-position: -64px -32px;
|
||||
background-position: -32px -32px;
|
||||
}
|
||||
.sidebarForkIcon:hover {
|
||||
background-position: -96px -32px;
|
||||
background-position: -64px -32px;
|
||||
}
|
||||
.addMap:hover {
|
||||
background-position: -128px -32px;
|
||||
background-position: -96px -32px;
|
||||
margin-right:10px;
|
||||
}
|
||||
|
||||
|
@ -325,7 +338,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 +377,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 +386,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 +395,9 @@
|
|||
.mapPage .mapInfoIcon {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.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 +451,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 +464,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 +472,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 {
|
||||
.mapInfoIcon:hover .tooltipsAbove, .openCheatsheet:hover .tooltipsAbove, .chat-button:hover .tooltips, .importDialog:hover .tooltipsUnder, .starMap:hover .tooltipsAbove, .openMetacodeSwitcher:hover .tooltipsAbove, .pinCarousel:not(.isPinned):hover .tooltipsAbove.helpPin, .pinCarousel.isPinned:hover .tooltipsAbove.helpUnpin {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
@ -514,6 +528,10 @@
|
|||
font-style: normal;
|
||||
}
|
||||
|
||||
.importDialog .tooltipsUnder {
|
||||
left: -22px;
|
||||
}
|
||||
|
||||
.sidebarFilterIcon .tooltipsUnder {
|
||||
margin-left: -4px;
|
||||
}
|
||||
|
@ -522,6 +540,29 @@
|
|||
margin-left: -34px;
|
||||
}
|
||||
|
||||
.openMetacodeSwitcher .tooltipsAbove {
|
||||
left: -50px;
|
||||
top: -5px;
|
||||
}
|
||||
.pinCarousel .tooltipsAbove {
|
||||
top: -5px;
|
||||
}
|
||||
.pinCarousel .tooltipsAbove.helpPin {
|
||||
left: -24px;
|
||||
}
|
||||
.pinCarousel .tooltipsAbove.helpUnpin {
|
||||
left: -14px;
|
||||
}
|
||||
.openMetacodeSwitcher .tooltipsAbove:after {
|
||||
left: 50%;
|
||||
}
|
||||
.pinCarousel .tooltipsAbove.helpPin:after {
|
||||
left: 46%;
|
||||
}
|
||||
.pinCarousel .tooltipsAbove.helpUnpin:after {
|
||||
left: 42%;
|
||||
}
|
||||
|
||||
.sidebarForkIcon div:after{
|
||||
left: 45%;
|
||||
}
|
||||
|
@ -571,7 +612,7 @@
|
|||
border-bottom: 5px solid transparent;
|
||||
}
|
||||
|
||||
.sidebarFilterIcon div:after, .sidebarForkIcon div:after, .addMap div:after, .sidebarAccountIcon .tooltipsUnder:after {
|
||||
.importDialog div:after, .sidebarFilterIcon div:after, .sidebarForkIcon div:after, .addMap div:after, .sidebarAccountIcon .tooltipsUnder:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 40%;
|
||||
|
@ -586,7 +627,7 @@
|
|||
right: 37% !important;
|
||||
}
|
||||
|
||||
.mapInfoIcon div:after, .openCheatsheet div:after, .starMap div:after {
|
||||
.mapInfoIcon div:after, .openCheatsheet div:after, .starMap div:after, .openMetacodeSwitcher div:after, .pinCarousel div:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 76%;
|
||||
|
@ -600,7 +641,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;
|
||||
|
@ -609,7 +650,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;
|
||||
|
@ -627,15 +668,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 {
|
||||
|
@ -716,26 +756,34 @@
|
|||
top:5px;
|
||||
left:5px;
|
||||
}
|
||||
|
||||
.exploreMapsCenter .authedApps .exploreMapsIcon {
|
||||
background-image: url(<%= asset_data_uri('user_sprite.png') %>);
|
||||
background-position: 0 -32px;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
.authedApps:hover .exploreMapsIcon, .authedApps.active .exploreMapsIcon {
|
||||
background-position-x: -32px;
|
||||
}
|
||||
.myMaps:hover .exploreMapsIcon, .myMaps.active .exploreMapsIcon {
|
||||
background-position: -32px -32px;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
@ -339,6 +339,7 @@
|
|||
margin-top: 12px;
|
||||
padding: 2px 8px 0;
|
||||
text-align: left;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.chat-box .chat-messages .chat-message .chat-message-time {
|
||||
float: right;
|
||||
|
|
278
app/assets/stylesheets/mapcard.scss.erb
Normal file
|
@ -0,0 +1,278 @@
|
|||
/* 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;
|
||||
|
||||
div {
|
||||
background: url('<%= asset_path('metadata.png') %>') no-repeat;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.numTopicsIcon {
|
||||
background-position: 0 -32px;
|
||||
}
|
||||
.numStarsIcon {
|
||||
background-position: 0 0;
|
||||
}
|
||||
.numSynapsesIcon {
|
||||
background-position: -32px -32px;
|
||||
}
|
||||
.numContributorsIcon {
|
||||
background-position: -32px 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -2,16 +2,33 @@
|
|||
display: none;
|
||||
}
|
||||
|
||||
/* Smartphones (portrait and landscape) ----------- */
|
||||
@media only screen and (max-device-width : 480px) {
|
||||
.upperLeftUI, .upperRightUI, .openCheatsheet, .mapInfoIcon, .uv-icon, .chat-box, #exploreMapsHeader {
|
||||
@media only screen and (max-width : 720px) and (min-width : 504px) {
|
||||
.sidebarSearch .tt-hint, .sidebarSearch .sidebarSearchField {
|
||||
width: 160px !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width : 390px) {
|
||||
.map .mapCard .mobileMetadata {
|
||||
width: 190px;
|
||||
}
|
||||
}
|
||||
@media only screen and (min-width : 390px) {
|
||||
.map .mapCard .mobileMetadata {
|
||||
width: 390px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Smartphones (portrait and landscape) ----------- the minimum space that two map cards can fit side by side */
|
||||
@media only screen and (max-width : 504px) {
|
||||
.upperLeftUI, .upperRightUI, .openCheatsheet, .mapInfoIcon, .feedback-icon, .chat-box, #exploreMapsHeader {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
||||
#mobile_header {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
.homeWrapper {
|
||||
width: 96%;
|
||||
padding: 0 2%;
|
||||
|
@ -38,11 +55,11 @@
|
|||
.learnMoreCTA {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
||||
#yield {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
.new_session, .new_user, .edit_user, .login, .forgotPassword {
|
||||
position: relative;
|
||||
top: auto;
|
||||
|
@ -51,12 +68,12 @@
|
|||
padding: 16px 10%;
|
||||
margin: 50px auto 0 auto;
|
||||
}
|
||||
|
||||
|
||||
.centerGreyForm input[type="text"], .centerGreyForm input[type="email"], .centerGreyForm input[type="password"] {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.wrapper div.mapInfoBox {
|
||||
|
||||
.wrapper .mapInfoBox {
|
||||
position: fixed;
|
||||
top: 50px;
|
||||
right: 0px;
|
||||
|
@ -64,15 +81,20 @@
|
|||
width: 100%;
|
||||
max-width: 360px;
|
||||
}
|
||||
|
||||
|
||||
#wrapper .requestInvite {
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
||||
#exploreMaps > div {
|
||||
margin-top: 70px;
|
||||
}
|
||||
|
||||
.mapper {
|
||||
width: 100%;
|
||||
margin: 0 0 30px 0;
|
||||
}
|
||||
|
||||
.map.newMap {
|
||||
a {
|
||||
|
@ -94,35 +116,73 @@
|
|||
span {
|
||||
vertical-align: middle;
|
||||
padding: 16px;
|
||||
display: inline-block;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
.map.newMap:hover .newMapImage {
|
||||
background-position: 0 -40px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Smartphones (portrait) ----------- */
|
||||
@media only screen and (max-width : 400px) {
|
||||
|
||||
.map {
|
||||
width: 100%;
|
||||
margin: 0 0 30px 0;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.mapCard {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.mapCard .title {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.mapCard .mapScreenshot {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mapCard {
|
||||
height: auto;
|
||||
padding: 0;
|
||||
|
||||
&:hover {
|
||||
.mainContent {
|
||||
filter: none;
|
||||
}
|
||||
}
|
||||
.mobileHasMapper, .mobileHasConversation {
|
||||
.mapperList {
|
||||
padding: 8px 16px;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.mobileHasMapper {
|
||||
background: url('<%= asset_path('junto.png') %>') no-repeat 12px 0;
|
||||
}
|
||||
.mobileHasConversation {
|
||||
background: url('<%= asset_path('junto.gif') %>') no-repeat 12px 0;
|
||||
}
|
||||
.mobileMetadata {
|
||||
margin: 0 auto;
|
||||
}
|
||||
.title {
|
||||
text-align: left;
|
||||
display: block;
|
||||
height: auto;
|
||||
padding: 16px;
|
||||
}
|
||||
.desc {
|
||||
padding: 0 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#mobile_header {
|
||||
|
|
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
|
@ -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
|
|
@ -1,2 +0,0 @@
|
|||
class Api::MappingsController < API::RestfulController
|
||||
end
|
|
@ -1,2 +0,0 @@
|
|||
class Api::MapsController < API::RestfulController
|
||||
end
|
|
@ -1,50 +0,0 @@
|
|||
class API::RestfulController < ActionController::Base
|
||||
include Pundit
|
||||
include PunditExtra
|
||||
|
||||
snorlax_used_rest!
|
||||
|
||||
load_and_authorize_resource only: [:show, :update, :destroy]
|
||||
|
||||
def create
|
||||
instantiate_resource
|
||||
resource.user = current_user
|
||||
authorize resource
|
||||
create_action
|
||||
respond_with_resource
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def resource_serializer
|
||||
"new_#{resource_name}_serializer".camelize.constantize
|
||||
end
|
||||
|
||||
def accessible_records
|
||||
if current_user
|
||||
visible_records
|
||||
else
|
||||
public_records
|
||||
end
|
||||
end
|
||||
|
||||
def current_user
|
||||
super || token_user || doorkeeper_user || nil
|
||||
end
|
||||
|
||||
def token_user
|
||||
token = params[:access_token]
|
||||
access_token = Token.find_by_token(token)
|
||||
@token_user ||= access_token.user if access_token
|
||||
end
|
||||
|
||||
def doorkeeper_user
|
||||
return unless doorkeeper_token.present?
|
||||
doorkeeper_render_error unless valid_doorkeeper_token?
|
||||
@doorkeeper_user ||= User.find(doorkeeper_token.resource_owner_id)
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
@permitted_params ||= PermittedParams.new(params)
|
||||
end
|
||||
end
|
|
@ -1,2 +0,0 @@
|
|||
class Api::SynapsesController < API::RestfulController
|
||||
end
|
|
@ -1,17 +0,0 @@
|
|||
class Api::TokensController < API::RestfulController
|
||||
def my_tokens
|
||||
raise Pundit::NotAuthorizedError unless current_user
|
||||
instantiate_collection page_collection: false, timeframe_collection: false
|
||||
respond_with_collection
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def resource_serializer
|
||||
"#{resource_name}_serializer".camelize.constantize
|
||||
end
|
||||
|
||||
def visible_records
|
||||
current_user.tokens
|
||||
end
|
||||
end
|
|
@ -1,2 +0,0 @@
|
|||
class Api::TopicsController < API::RestfulController
|
||||
end
|
12
app/controllers/api/v1/deprecated_controller.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
module Api
|
||||
module V1
|
||||
class DeprecatedController < ApplicationController
|
||||
def deprecated
|
||||
render json: {
|
||||
error: '/api/v1 has been deprecated! Please use /api/v2 instead.'
|
||||
}, status: :gone
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
10
app/controllers/api/v2/mappings_controller.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
module Api
|
||||
module V2
|
||||
class MappingsController < RestfulController
|
||||
def searchable_columns
|
||||
[]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
15
app/controllers/api/v2/maps_controller.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
module Api
|
||||
module V2
|
||||
class MapsController < RestfulController
|
||||
def searchable_columns
|
||||
[:name, :desc]
|
||||
end
|
||||
|
||||
def apply_filters(collection)
|
||||
collection = collection.where(user_id: params[:user_id]) if params[:user_id]
|
||||
collection
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
10
app/controllers/api/v2/metacodes_controller.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
module Api
|
||||
module V2
|
||||
class MetacodesController < RestfulController
|
||||
def searchable_columns
|
||||
[:name]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
198
app/controllers/api/v2/restful_controller.rb
Normal file
|
@ -0,0 +1,198 @@
|
|||
# frozen_string_literal: true
|
||||
module Api
|
||||
module V2
|
||||
class RestfulController < ActionController::Base
|
||||
include Pundit
|
||||
include PunditExtra
|
||||
|
||||
snorlax_used_rest!
|
||||
|
||||
before_action :load_resource, only: [:show, :update, :destroy]
|
||||
after_action :verify_authorized
|
||||
|
||||
def index
|
||||
authorize resource_class
|
||||
instantiate_collection
|
||||
respond_with_collection
|
||||
end
|
||||
|
||||
def create
|
||||
instantiate_resource
|
||||
resource.user = current_user if current_user.present?
|
||||
authorize resource
|
||||
create_action
|
||||
respond_with_resource
|
||||
end
|
||||
|
||||
def destroy
|
||||
destroy_action
|
||||
head :no_content
|
||||
end
|
||||
|
||||
def catch_404
|
||||
skip_authorization
|
||||
render json: { error: '404 Not found' }, status: :not_found
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def accessible_records
|
||||
if current_user
|
||||
visible_records
|
||||
else
|
||||
public_records
|
||||
end
|
||||
end
|
||||
|
||||
def current_user
|
||||
token_user || doorkeeper_user || super
|
||||
end
|
||||
|
||||
def load_resource
|
||||
super
|
||||
authorize resource
|
||||
end
|
||||
|
||||
def resource_serializer
|
||||
"Api::V2::#{resource_name.camelize}Serializer".constantize
|
||||
end
|
||||
|
||||
def respond_with_resource(scope: default_scope, serializer: resource_serializer,
|
||||
root: serializer_root)
|
||||
if resource.errors.empty?
|
||||
render json: resource, scope: scope, serializer: serializer, root: root
|
||||
else
|
||||
respond_with_errors
|
||||
end
|
||||
end
|
||||
|
||||
def respond_with_collection(resources: collection, scope: default_scope,
|
||||
serializer: resource_serializer, root: serializer_root)
|
||||
pagination_link_headers!(pagination(resources))
|
||||
render json: resources, scope: scope, each_serializer: serializer, root: root,
|
||||
meta: pagination(resources), meta_key: :page
|
||||
end
|
||||
|
||||
def default_scope
|
||||
{
|
||||
embeds: embeds,
|
||||
current_user: current_user
|
||||
}
|
||||
end
|
||||
|
||||
def embeds
|
||||
(params[:embed] || '').split(',').map(&:to_sym)
|
||||
end
|
||||
|
||||
def token_user
|
||||
token = params[:access_token]
|
||||
access_token = Token.find_by_token(token)
|
||||
@token_user ||= access_token.user if access_token
|
||||
end
|
||||
|
||||
def doorkeeper_user
|
||||
return unless doorkeeper_token.present?
|
||||
doorkeeper_render_error unless valid_doorkeeper_token?
|
||||
@doorkeeper_user ||= User.find(doorkeeper_token.resource_owner_id)
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
@permitted_params ||= PermittedParams.new(params)
|
||||
end
|
||||
|
||||
def serializer_root
|
||||
'data'
|
||||
end
|
||||
|
||||
def pagination(collection)
|
||||
return @pagination_data unless @pagination_data.nil?
|
||||
|
||||
current_page = (params[:page] || 1).to_i
|
||||
per = (params[:per] || 25).to_i
|
||||
total_pages = (collection.total_count.to_f / per).ceil
|
||||
@pagination_data = {
|
||||
current_page: current_page,
|
||||
next_page: current_page < total_pages ? current_page + 1 : 0,
|
||||
prev_page: current_page > 1 ? current_page - 1 : 0,
|
||||
total_pages: total_pages,
|
||||
total_count: collection.total_count,
|
||||
per: per
|
||||
}
|
||||
end
|
||||
|
||||
def pagination_link_headers!(data)
|
||||
base_url = request.base_url + request.path
|
||||
old_query = request.query_parameters
|
||||
nxt = old_query.merge(page: data[:next_page]).map { |x| x.join('=') }.join('&')
|
||||
prev = old_query.merge(page: data[:prev_page]).map { |x| x.join('=') }.join('&')
|
||||
last = old_query.merge(page: data[:total_pages]).map { |x| x.join('=') }.join('&')
|
||||
|
||||
response.headers['Link'] = [
|
||||
%(<#{base_url}?#{nxt}>; rel="next"),
|
||||
%(<#{base_url}?#{prev}>; rel="prev"),
|
||||
%(<#{base_url}?#{last}>; rel="last")
|
||||
].join(',')
|
||||
response.headers['X-Total-Pages'] = data[:total_pages].to_s
|
||||
response.headers['X-Total-Count'] = data[:total_count].to_s
|
||||
response.headers['X-Per-Page'] = data[:per].to_s
|
||||
end
|
||||
|
||||
def instantiate_collection
|
||||
collection = accessible_records
|
||||
collection = yield collection if block_given?
|
||||
collection = search_by_q(collection) if params[:q]
|
||||
collection = apply_filters(collection)
|
||||
collection = order_by_sort(collection) if params[:sort]
|
||||
collection = collection.page(params[:page]).per(params[:per])
|
||||
self.collection = collection
|
||||
end
|
||||
|
||||
# override this method to explicitly set searchable columns
|
||||
def searchable_columns
|
||||
columns = resource_class.columns.select do |column|
|
||||
column.type == :text || column.type == :string
|
||||
end
|
||||
columns.map(&:name)
|
||||
end
|
||||
|
||||
# thanks to http://stackoverflow.com/questions/4430578
|
||||
def search_by_q(collection)
|
||||
table = resource_class.arel_table
|
||||
safe_query = "%#{params[:q].gsub(/[%_]/, '\\\\\0')}%"
|
||||
search_column = -> (column) { table[column].matches(safe_query) }
|
||||
|
||||
condition = searchable_columns.reduce(nil) do |prev, column|
|
||||
next search_column.call(column) if prev.nil?
|
||||
search_column.call(column).or(prev)
|
||||
end
|
||||
collection.where(condition)
|
||||
end
|
||||
|
||||
def apply_filters(collection)
|
||||
# override this function for specific filters
|
||||
collection
|
||||
end
|
||||
|
||||
def order_by_sort(collection)
|
||||
builder = collection
|
||||
sorts = params[:sort].split(',')
|
||||
sorts.each do |sort|
|
||||
direction = sort.starts_with?('-') ? 'desc' : 'asc'
|
||||
sort = sort.sub(/^-/, '')
|
||||
if resource_class.columns.map(&:name).include?(sort)
|
||||
builder = builder.order(sort => direction)
|
||||
end
|
||||
end
|
||||
builder
|
||||
end
|
||||
|
||||
def visible_records
|
||||
policy_scope(resource_class)
|
||||
end
|
||||
|
||||
def public_records
|
||||
policy_scope(resource_class)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
21
app/controllers/api/v2/sessions_controller.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
module Api
|
||||
module V2
|
||||
class SessionsController < ApplicationController
|
||||
def create
|
||||
@user = User.find_by(email: params[:email])
|
||||
if @user&.valid_password(params[:password])
|
||||
sign_in(@user)
|
||||
render json: @user
|
||||
else
|
||||
render json: { error: 'Error' }
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
sign_out
|
||||
head :no_content
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
29
app/controllers/api/v2/stars_controller.rb
Normal file
|
@ -0,0 +1,29 @@
|
|||
# frozen_string_literal: true
|
||||
module Api
|
||||
module V2
|
||||
class StarsController < RestfulController
|
||||
skip_before_action :load_resource
|
||||
|
||||
def create
|
||||
@map = Map.find(params[:id])
|
||||
@star = Star.new(user: current_user, map: @map)
|
||||
authorize @map, :star?
|
||||
create_action
|
||||
|
||||
if @star.errors.empty?
|
||||
render json: @map, scope: default_scope, serializer: MapSerializer, root: serializer_root
|
||||
else
|
||||
respond_with_errors
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@map = Map.find(params[:id])
|
||||
authorize @map, :unstar?
|
||||
@star = @map.stars.find_by(user: current_user)
|
||||
@star.destroy if @star.present?
|
||||
head :no_content
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
10
app/controllers/api/v2/synapses_controller.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
module Api
|
||||
module V2
|
||||
class SynapsesController < RestfulController
|
||||
def searchable_columns
|
||||
[:desc]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
23
app/controllers/api/v2/tokens_controller.rb
Normal file
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
module Api
|
||||
module V2
|
||||
class TokensController < RestfulController
|
||||
def searchable_columns
|
||||
[:description]
|
||||
end
|
||||
|
||||
def create
|
||||
if params[:token].blank?
|
||||
self.resource = resource_class.new
|
||||
else
|
||||
instantiate_resource
|
||||
end
|
||||
|
||||
resource.user = current_user if current_user.present?
|
||||
authorize resource
|
||||
create_action
|
||||
respond_with_resource
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
10
app/controllers/api/v2/topics_controller.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
module Api
|
||||
module V2
|
||||
class TopicsController < RestfulController
|
||||
def searchable_columns
|
||||
[:name, :desc, :link]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
24
app/controllers/api/v2/users_controller.rb
Normal file
|
@ -0,0 +1,24 @@
|
|||
# frozen_string_literal: true
|
||||
module Api
|
||||
module V2
|
||||
class UsersController < RestfulController
|
||||
def current
|
||||
@user = current_user
|
||||
authorize @user
|
||||
show # delegate to the normal show function
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def searchable_columns
|
||||
[:name]
|
||||
end
|
||||
|
||||
# only ask serializer to return is_admin field if we're on the
|
||||
# current_user action
|
||||
def default_scope
|
||||
super.merge(show_full_user: action_name == 'current')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,3 +1,4 @@
|
|||
# frozen_string_literal: true
|
||||
class ApplicationController < ActionController::Base
|
||||
include ApplicationHelper
|
||||
include Pundit
|
||||
|
@ -5,7 +6,8 @@ class ApplicationController < ActionController::Base
|
|||
rescue_from Pundit::NotAuthorizedError, with: :handle_unauthorized
|
||||
protect_from_forgery(with: :exception)
|
||||
|
||||
before_action :get_invite_link
|
||||
before_action :invite_link
|
||||
before_action :prepare_exception_notifier
|
||||
after_action :allow_embedding
|
||||
|
||||
def default_serializer_options
|
||||
|
@ -19,51 +21,39 @@ 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
|
||||
|
||||
private
|
||||
|
||||
def get_invite_link
|
||||
def invite_link
|
||||
@invite_link = "#{request.base_url}/join" + (current_user ? "?code=#{current_user.code}" : '')
|
||||
end
|
||||
|
||||
def require_no_user
|
||||
if authenticated?
|
||||
redirect_to edit_user_path(user), notice: 'You must be logged out.'
|
||||
return false
|
||||
end
|
||||
return true unless authenticated?
|
||||
redirect_to edit_user_path(user), notice: 'You must be logged out.'
|
||||
return false
|
||||
end
|
||||
|
||||
def require_user
|
||||
unless authenticated?
|
||||
redirect_to new_user_session_path, notice: 'You must be logged in.'
|
||||
return false
|
||||
end
|
||||
return true if authenticated?
|
||||
redirect_to sign_in_path, notice: 'You must be logged in.'
|
||||
return false
|
||||
end
|
||||
|
||||
def require_admin
|
||||
unless authenticated? && admin?
|
||||
redirect_to root_url, notice: 'You need to be an admin for that.'
|
||||
return false
|
||||
end
|
||||
return true if authenticated? && admin?
|
||||
redirect_to root_url, notice: 'You need to be an admin for that.'
|
||||
false
|
||||
end
|
||||
|
||||
def user
|
||||
|
@ -84,4 +74,10 @@ class ApplicationController < ActionController::Base
|
|||
# or allow a whitelist
|
||||
# response.headers['X-Frame-Options'] = 'ALLOW-FROM http://blog.metamaps.cc'
|
||||
end
|
||||
|
||||
def prepare_exception_notifier
|
||||
request.env['exception_notifier.exception_data'] = {
|
||||
current_user: current_user
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
89
app/controllers/explore_controller.rb
Normal file
|
@ -0,0 +1,89 @@
|
|||
# frozen_string_literal: true
|
||||
class ExploreController < ApplicationController
|
||||
before_action :require_authentication, only: [:mine, :shared, :starred]
|
||||
before_action :authorize_explore
|
||||
after_action :verify_authorized
|
||||
after_action :verify_policy_scoped
|
||||
|
||||
respond_to :html, :json, :csv
|
||||
|
||||
# GET /explore/active
|
||||
def active
|
||||
@maps = map_scope(Map.where.not(name: 'Untitled Map').where.not(permission: 'private'))
|
||||
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
# root url => main/home. main/home renders maps/activemaps view.
|
||||
redirect_to(root_url) && return if authenticated?
|
||||
respond_with(@maps, @user)
|
||||
end
|
||||
format.json { render json: @maps.to_json }
|
||||
end
|
||||
end
|
||||
|
||||
# GET /explore/featured
|
||||
def featured
|
||||
@maps = map_scope(Map.where(featured: true))
|
||||
|
||||
respond_to do |format|
|
||||
format.html { respond_with(@maps, @user) }
|
||||
format.json { render json: @maps.to_json }
|
||||
end
|
||||
end
|
||||
|
||||
# GET /explore/mine
|
||||
def mine
|
||||
@maps = map_scope(Map.where(user_id: current_user.id))
|
||||
|
||||
respond_to do |format|
|
||||
format.html { respond_with(@maps, @user) }
|
||||
format.json { render json: @maps.to_json }
|
||||
end
|
||||
end
|
||||
|
||||
# GET /explore/shared
|
||||
def shared
|
||||
@maps = map_scope(Map.where(id: current_user.shared_maps.map(&:id)))
|
||||
|
||||
respond_to do |format|
|
||||
format.html { respond_with(@maps, @user) }
|
||||
format.json { render json: @maps.to_json }
|
||||
end
|
||||
end
|
||||
|
||||
# GET /explore/starred
|
||||
def starred
|
||||
@maps = map_scope(Map.where(id: current_user.stars.map(&:map_id)))
|
||||
|
||||
respond_to do |format|
|
||||
format.html { respond_with(@maps, @user) }
|
||||
format.json { render json: @maps.to_json }
|
||||
end
|
||||
end
|
||||
|
||||
# GET /explore/mapper/:id
|
||||
def mapper
|
||||
@user = User.find(params[:id])
|
||||
@maps = map_scope(Map.where(user: @user))
|
||||
|
||||
respond_to do |format|
|
||||
format.html { respond_with(@maps, @user) }
|
||||
format.json { render json: @maps.to_json }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def map_scope(scope)
|
||||
policy_scope(scope).order(updated_at: :desc).page(params[:page]).per(20)
|
||||
end
|
||||
|
||||
def authorize_explore
|
||||
authorize :Explore
|
||||
end
|
||||
|
||||
def require_authentication
|
||||
# skip_policy_scope
|
||||
redirect_to explore_active_path unless authenticated?
|
||||
end
|
||||
end
|
37
app/controllers/hacks_controller.rb
Normal file
|
@ -0,0 +1,37 @@
|
|||
# bad code that should be checked over before entering one of the
|
||||
# nice files from the right side of this repo
|
||||
class HacksController < ApplicationController
|
||||
include ActionView::Helpers::TextHelper # string truncate method
|
||||
|
||||
# rate limited by rack-attack - currently 5r/s
|
||||
# TODO: what else can we do to make get_with_redirects safer?
|
||||
def load_url_title
|
||||
authorize :Hack
|
||||
url = params[:url]
|
||||
response, url = get_with_redirects(url)
|
||||
title = get_encoded_title(response)
|
||||
render json: { success: true, title: title, url: url }
|
||||
rescue StandardError => e
|
||||
render json: { success: false }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get_with_redirects(url)
|
||||
uri = URI.parse(url)
|
||||
response = Net::HTTP.get_response(uri)
|
||||
while response.code == '301'
|
||||
uri = URI.parse(response['location'])
|
||||
response = Net::HTTP.get_response(uri)
|
||||
end
|
||||
[response, uri.to_s]
|
||||
end
|
||||
|
||||
def get_encoded_title(http_response)
|
||||
title = http_response.body.sub(/.*<title>(.*)<\/title>.*/m, '\1')
|
||||
charset = http_response['content-type'].sub(/.*charset=(.*);?.*/, '\1')
|
||||
charset = nil if charset == 'text/html'
|
||||
title = title.force_encoding(charset) if charset
|
||||
truncate(title, length: 140)
|
||||
end
|
||||
end
|
|
@ -1,171 +1,30 @@
|
|||
# frozen_string_literal: true
|
||||
class MainController < ApplicationController
|
||||
include TopicsHelper
|
||||
include MapsHelper
|
||||
include UsersHelper
|
||||
include SynapsesHelper
|
||||
before_action :authorize_main
|
||||
after_action :verify_authorized
|
||||
|
||||
after_action :verify_policy_scoped, except: [:requestinvite, :searchmappers]
|
||||
|
||||
respond_to :html, :json
|
||||
|
||||
# home page
|
||||
# GET /
|
||||
def home
|
||||
@maps = policy_scope(Map).order('updated_at DESC').page(1).per(20)
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
if !authenticated?
|
||||
render 'main/home'
|
||||
else
|
||||
render 'maps/activemaps'
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
### SEARCHING ###
|
||||
|
||||
# get /search/topics?term=SOMETERM
|
||||
def searchtopics
|
||||
term = params[:term]
|
||||
user = params[:user] ? params[:user] : false
|
||||
|
||||
if term && !term.empty? && term.downcase[0..3] != 'map:' && term.downcase[0..6] != 'mapper:' && !term.casecmp('topic:').zero?
|
||||
|
||||
# remove "topic:" if appended at beginning
|
||||
term = term[6..-1] if term.downcase[0..5] == 'topic:'
|
||||
|
||||
# if desc: search desc instead
|
||||
desc = false
|
||||
if term.downcase[0..4] == 'desc:'
|
||||
term = term[5..-1]
|
||||
desc = true
|
||||
end
|
||||
|
||||
# if link: search link instead
|
||||
link = false
|
||||
if term.downcase[0..4] == 'link:'
|
||||
term = term[5..-1]
|
||||
link = true
|
||||
end
|
||||
|
||||
# check whether there's a filter by metacode as part of the query
|
||||
filterByMetacode = false
|
||||
Metacode.all.each do |m|
|
||||
lOne = m.name.length + 1
|
||||
lTwo = m.name.length
|
||||
|
||||
if term.downcase[0..lTwo] == m.name.downcase + ':'
|
||||
term = term[lOne..-1]
|
||||
filterByMetacode = m
|
||||
end
|
||||
end
|
||||
|
||||
search = '%' + term.downcase + '%'
|
||||
builder = policy_scope(Topic)
|
||||
|
||||
if filterByMetacode
|
||||
if term == ''
|
||||
builder = builder.none
|
||||
format.html do
|
||||
if authenticated?
|
||||
@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
|
||||
builder = builder.where('LOWER("name") like ? OR
|
||||
LOWER("desc") like ? OR
|
||||
LOWER("link") like ?', search, search, search)
|
||||
builder = builder.where(metacode_id: filterByMetacode.id)
|
||||
render 'main/home'
|
||||
end
|
||||
elsif desc
|
||||
builder = builder.where('LOWER("desc") like ?', search)
|
||||
elsif link
|
||||
builder = builder.where('LOWER("link") like ?', search)
|
||||
else # regular case, just search the name
|
||||
builder = builder.where('LOWER("name") like ? OR
|
||||
LOWER("desc") like ? OR
|
||||
LOWER("link") like ?', search, search, search)
|
||||
end
|
||||
|
||||
builder = builder.where(user: user) if user
|
||||
@topics = builder.order(:name)
|
||||
else
|
||||
@topics = []
|
||||
end
|
||||
|
||||
render json: autocomplete_array_json(@topics)
|
||||
end
|
||||
|
||||
# get /search/maps?term=SOMETERM
|
||||
def searchmaps
|
||||
term = params[:term]
|
||||
user = params[:user] ? params[:user] : nil
|
||||
|
||||
if term && !term.empty? && term.downcase[0..5] != 'topic:' && term.downcase[0..6] != 'mapper:' && !term.casecmp('map:').zero?
|
||||
|
||||
# remove "map:" if appended at beginning
|
||||
term = term[4..-1] if term.downcase[0..3] == 'map:'
|
||||
|
||||
# if desc: search desc instead
|
||||
desc = false
|
||||
if term.downcase[0..4] == 'desc:'
|
||||
term = term[5..-1]
|
||||
desc = true
|
||||
end
|
||||
|
||||
search = '%' + term.downcase + '%'
|
||||
builder = policy_scope(Map)
|
||||
|
||||
builder = if desc
|
||||
builder.where('LOWER("desc") like ?', search)
|
||||
else
|
||||
builder.where('LOWER("name") like ?', search)
|
||||
end
|
||||
builder = builder.where(user: user) if user
|
||||
@maps = builder.order(:name)
|
||||
else
|
||||
@maps = []
|
||||
end
|
||||
|
||||
render json: autocomplete_map_array_json(@maps)
|
||||
# GET /request
|
||||
def requestinvite
|
||||
end
|
||||
|
||||
# get /search/mappers?term=SOMETERM
|
||||
def searchmappers
|
||||
term = params[:term]
|
||||
if term && !term.empty? && term.downcase[0..3] != 'map:' && term.downcase[0..5] != 'topic:' && !term.casecmp('mapper:').zero?
|
||||
private
|
||||
|
||||
# remove "mapper:" if appended at beginning
|
||||
term = term[7..-1] if term.downcase[0..6] == 'mapper:'
|
||||
search = term.downcase + '%'
|
||||
|
||||
skip_policy_scope # TODO: builder = policy_scope(User)
|
||||
builder = User.where('LOWER("name") like ?', search)
|
||||
@mappers = builder.order(:name)
|
||||
else
|
||||
@mappers = []
|
||||
end
|
||||
render json: autocomplete_user_array_json(@mappers)
|
||||
end
|
||||
|
||||
# get /search/synapses?term=SOMETERM OR
|
||||
# get /search/synapses?topic1id=SOMEID&topic2id=SOMEID
|
||||
def searchsynapses
|
||||
term = params[:term]
|
||||
topic1id = params[:topic1id]
|
||||
topic2id = params[:topic2id]
|
||||
|
||||
if term && !term.empty?
|
||||
@synapses = policy_scope(Synapse).where('LOWER("desc") like ?', '%' + term.downcase + '%').order('"desc"')
|
||||
|
||||
@synapses = @synapses.uniq(&:desc)
|
||||
elsif topic1id && !topic1id.empty?
|
||||
@one = policy_scope(Synapse).where('node1_id = ? AND node2_id = ?', topic1id, topic2id)
|
||||
@two = policy_scope(Synapse).where('node2_id = ? AND node1_id = ?', topic1id, topic2id)
|
||||
@synapses = @one + @two
|
||||
@synapses.sort! { |s1, s2| s1.desc <=> s2.desc }.to_a
|
||||
else
|
||||
@synapses = []
|
||||
end
|
||||
|
||||
# limit to 5 results
|
||||
@synapses = @synapses.slice(0, 5)
|
||||
|
||||
render json: autocomplete_synapse_array_json(@synapses)
|
||||
def authorize_main
|
||||
authorize :Main
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# frozen_string_literal: true
|
||||
class MappingsController < ApplicationController
|
||||
before_action :require_user, only: [:create, :update, :destroy]
|
||||
after_action :verify_authorized, except: :index
|
||||
|
|
|
@ -1,256 +1,58 @@
|
|||
# frozen_string_literal: true
|
||||
class MapsController < ApplicationController
|
||||
before_action :require_user, only: [:create, :update, :access, :star, :unstar, :screenshot, :events, :destroy]
|
||||
after_action :verify_authorized, except: [:activemaps, :featuredmaps, :mymaps, :sharedmaps, :starredmaps, :usermaps, :events]
|
||||
after_action :verify_policy_scoped, only: [:activemaps, :featuredmaps, :mymaps, :sharedmaps, :starredmaps, :usermaps]
|
||||
|
||||
respond_to :html, :json, :csv
|
||||
|
||||
autocomplete :map, :name, full: true, extra_data: [:user_id]
|
||||
|
||||
# GET /explore/active
|
||||
def activemaps
|
||||
page = params[:page].present? ? params[:page] : 1
|
||||
@maps = policy_scope(Map).order('updated_at DESC')
|
||||
.page(page).per(20)
|
||||
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
|
||||
def show
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
# root url => main/home. main/home renders maps/activemaps view.
|
||||
redirect_to(root_url) && return if authenticated?
|
||||
respond_with(@maps, @user)
|
||||
@allmappers = @map.contributors
|
||||
@allcollaborators = @map.editors
|
||||
@alltopics = policy_scope(@map.topics)
|
||||
@allsynapses = policy_scope(@map.synapses)
|
||||
@allmappings = policy_scope(@map.mappings)
|
||||
@allmessages = @map.messages.sort_by(&:created_at)
|
||||
@allstars = @map.stars
|
||||
@allrequests = @map.access_requests
|
||||
end
|
||||
format.json { render json: @maps }
|
||||
end
|
||||
end
|
||||
|
||||
# GET /explore/featured
|
||||
def featuredmaps
|
||||
page = params[:page].present? ? params[:page] : 1
|
||||
@maps = policy_scope(
|
||||
Map.where('maps.featured = ? AND maps.permission != ?',
|
||||
true, 'private')
|
||||
).order('updated_at DESC').page(page).per(20)
|
||||
|
||||
respond_to do |format|
|
||||
format.html { respond_with(@maps, @user) }
|
||||
format.json { render json: @maps }
|
||||
end
|
||||
end
|
||||
|
||||
# GET /explore/mine
|
||||
def mymaps
|
||||
unless authenticated?
|
||||
skip_policy_scope
|
||||
return redirect_to explore_active_path
|
||||
end
|
||||
|
||||
page = params[:page].present? ? params[:page] : 1
|
||||
@maps = policy_scope(
|
||||
Map.where('maps.user_id = ?', current_user.id)
|
||||
).order('updated_at DESC').page(page).per(20)
|
||||
|
||||
respond_to do |format|
|
||||
format.html { respond_with(@maps, @user) }
|
||||
format.json { render json: @maps }
|
||||
end
|
||||
end
|
||||
|
||||
# GET /explore/shared
|
||||
def sharedmaps
|
||||
unless authenticated?
|
||||
skip_policy_scope
|
||||
return redirect_to explore_active_path
|
||||
end
|
||||
|
||||
page = params[:page].present? ? params[:page] : 1
|
||||
@maps = policy_scope(
|
||||
Map.where('maps.id IN (?)', current_user.shared_maps.map(&:id))
|
||||
).order('updated_at DESC').page(page).per(20)
|
||||
|
||||
respond_to do |format|
|
||||
format.html { respond_with(@maps, @user) }
|
||||
format.json { render json: @maps }
|
||||
end
|
||||
end
|
||||
|
||||
# GET /explore/starred
|
||||
def starredmaps
|
||||
unless authenticated?
|
||||
skip_policy_scope
|
||||
return redirect_to explore_active_path
|
||||
end
|
||||
|
||||
page = params[:page].present? ? params[:page] : 1
|
||||
stars = current_user.stars.map(&:map_id)
|
||||
@maps = policy_scope(
|
||||
Map.where('maps.id IN (?)', stars)
|
||||
).order('updated_at DESC').page(page).per(20)
|
||||
|
||||
respond_to do |format|
|
||||
format.html { respond_with(@maps, @user) }
|
||||
format.json { render json: @maps }
|
||||
end
|
||||
end
|
||||
|
||||
# GET /explore/mapper/:id
|
||||
def usermaps
|
||||
page = params[:page].present? ? params[:page] : 1
|
||||
@user = User.find(params[:id])
|
||||
@maps = policy_scope(Map.where(user: @user))
|
||||
.order('updated_at DESC').page(page).per(20)
|
||||
|
||||
respond_to do |format|
|
||||
format.html { respond_with(@maps, @user) }
|
||||
format.json { render json: @maps }
|
||||
format.json { render json: @map }
|
||||
format.csv { redirect_to action: :export, format: :csv }
|
||||
end
|
||||
end
|
||||
|
||||
# GET maps/new
|
||||
def new
|
||||
@map = Map.new(name: "Untitled Map", permission: "public", arranged: true)
|
||||
@map = Map.new(name: 'Untitled Map', permission: 'public', arranged: true)
|
||||
authorize @map
|
||||
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
@map.user = current_user
|
||||
@map.save
|
||||
redirect_to(map_path(@map) + '?new')
|
||||
redirect_to(map_path(@map) + '?new')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# GET maps/:id
|
||||
def show
|
||||
@map = Map.find(params[:id])
|
||||
authorize @map
|
||||
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
@allmappers = @map.contributors
|
||||
@allcollaborators = @map.editors
|
||||
@alltopics = @map.topics.to_a.delete_if { |t| !policy(t).show? }
|
||||
@allsynapses = @map.synapses.to_a.delete_if { |s| !policy(s).show? }
|
||||
@allmappings = @map.mappings.to_a.delete_if { |m| !policy(m).show? }
|
||||
@allmessages = @map.messages.sort_by(&:created_at)
|
||||
@allstars = @map.stars
|
||||
|
||||
respond_with(@allmappers, @allcollaborators, @allmappings, @allsynapses, @alltopics, @allmessages, @allstars, @map)
|
||||
end
|
||||
format.json { render json: @map }
|
||||
format.csv { redirect_to action: :export, format: :csv }
|
||||
format.xls { redirect_to action: :export, format: :xls }
|
||||
end
|
||||
end
|
||||
|
||||
# GET maps/:id/export
|
||||
def export
|
||||
map = Map.find(params[:id])
|
||||
authorize map
|
||||
exporter = MapExportService.new(current_user, map)
|
||||
respond_to do |format|
|
||||
format.json { render json: exporter.json }
|
||||
format.csv { send_data exporter.csv }
|
||||
format.xls { @spreadsheet = exporter.xls }
|
||||
end
|
||||
end
|
||||
|
||||
# POST maps/:id/events/:event
|
||||
def events
|
||||
map = Map.find(params[:id])
|
||||
authorize map
|
||||
|
||||
valid_event = false
|
||||
if params[:event] == 'conversation'
|
||||
Events::ConversationStartedOnMap.publish!(map, current_user)
|
||||
valid_event = true
|
||||
elsif params[:event] == 'user_presence'
|
||||
Events::UserPresentOnMap.publish!(map, current_user)
|
||||
valid_event = true
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
head :ok if valid_event
|
||||
head :bad_request unless valid_event
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# GET maps/:id/contains
|
||||
def contains
|
||||
@map = Map.find(params[:id])
|
||||
authorize @map
|
||||
|
||||
@allmappers = @map.contributors
|
||||
@allcollaborators = @map.editors
|
||||
@alltopics = @map.topics.to_a.delete_if { |t| !policy(t).show? }
|
||||
@allsynapses = @map.synapses.to_a.delete_if { |s| !policy(s).show? }
|
||||
@allmappings = @map.mappings.to_a.delete_if { |m| !policy(m).show? }
|
||||
|
||||
@json = {}
|
||||
@json['map'] = @map
|
||||
@json['topics'] = @alltopics
|
||||
@json['synapses'] = @allsynapses
|
||||
@json['mappings'] = @allmappings
|
||||
@json['mappers'] = @allmappers
|
||||
@json['collaborators'] = @allcollaborators
|
||||
@json['messages'] = @map.messages.sort_by(&:created_at)
|
||||
@json['stars'] = @map.stars
|
||||
|
||||
respond_to do |format|
|
||||
format.json { render json: @json }
|
||||
end
|
||||
end
|
||||
|
||||
# POST maps
|
||||
def create
|
||||
@user = current_user
|
||||
@map = Map.new
|
||||
@map.name = params[:name]
|
||||
@map.desc = params[:desc]
|
||||
@map.permission = params[:permission]
|
||||
@map.user = @user
|
||||
@map = Map.new(create_map_params)
|
||||
@map.user = current_user
|
||||
@map.arranged = false
|
||||
authorize @map
|
||||
|
||||
if params[:topicsToMap]
|
||||
@all = params[:topicsToMap]
|
||||
@all = @all.split(',')
|
||||
@all.each do |topic|
|
||||
topic = topic.split('/')
|
||||
mapping = Mapping.new
|
||||
mapping.map = @map
|
||||
mapping.user = @user
|
||||
mapping.mappable = Topic.find(topic[0])
|
||||
mapping.xloc = topic[1]
|
||||
mapping.yloc = topic[2]
|
||||
authorize mapping, :create?
|
||||
mapping.save
|
||||
end
|
||||
|
||||
if params[:synapsesToMap]
|
||||
@synAll = params[:synapsesToMap]
|
||||
@synAll = @synAll.split(',')
|
||||
@synAll.each do |synapse_id|
|
||||
mapping = Mapping.new
|
||||
mapping.map = @map
|
||||
mapping.user = @user
|
||||
mapping.mappable = Synapse.find(synapse_id)
|
||||
authorize mapping, :create?
|
||||
mapping.save
|
||||
end
|
||||
end
|
||||
|
||||
if params[:topicsToMap].present?
|
||||
create_topics!
|
||||
create_synapses! if params[:synapsesToMap].present?
|
||||
@map.arranged = true
|
||||
end
|
||||
|
||||
authorize @map
|
||||
|
||||
if @map.save
|
||||
respond_to do |format|
|
||||
respond_to do |format|
|
||||
if @map.save
|
||||
format.json { render json: @map }
|
||||
end
|
||||
else
|
||||
respond_to do |format|
|
||||
else
|
||||
format.json { render json: 'invalid params' }
|
||||
end
|
||||
end
|
||||
|
@ -258,11 +60,8 @@ class MapsController < ApplicationController
|
|||
|
||||
# PUT maps/:id
|
||||
def update
|
||||
@map = Map.find(params[:id])
|
||||
authorize @map
|
||||
|
||||
respond_to do |format|
|
||||
if @map.update_attributes(map_params)
|
||||
if @map.update_attributes(update_map_params)
|
||||
format.json { head :no_content }
|
||||
else
|
||||
format.json { render json: @map.errors, status: :unprocessable_entity }
|
||||
|
@ -270,93 +69,8 @@ class MapsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
# POST maps/:id/access
|
||||
def access
|
||||
@map = Map.find(params[:id])
|
||||
authorize @map
|
||||
userIds = params[:access] || []
|
||||
added = userIds.select do |uid|
|
||||
user = User.find(uid)
|
||||
if user.nil? || (current_user && user == current_user)
|
||||
false
|
||||
else
|
||||
!@map.collaborators.include?(user)
|
||||
end
|
||||
end
|
||||
removed = @map.collaborators.select { |user| !userIds.include?(user.id.to_s) }.map(&:id)
|
||||
added.each do |uid|
|
||||
UserMap.create(user_id: uid.to_i, map_id: @map.id)
|
||||
user = User.find(uid.to_i)
|
||||
MapMailer.invite_to_edit_email(@map, current_user, user).deliver_later
|
||||
end
|
||||
removed.each do |uid|
|
||||
@map.user_maps.select { |um| um.user_id == uid }.each(&:destroy)
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: { message: 'Successfully altered edit permissions' }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# POST maps/:id/star
|
||||
def star
|
||||
@map = Map.find(params[:id])
|
||||
authorize @map
|
||||
star = Star.find_by_map_id_and_user_id(@map.id, current_user.id)
|
||||
if not star
|
||||
star = Star.create(map_id: @map.id, user_id: current_user.id)
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: { message: 'Successfully starred map' }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# POST maps/:id/unstar
|
||||
def unstar
|
||||
@map = Map.find(params[:id])
|
||||
authorize @map
|
||||
star = Star.find_by_map_id_and_user_id(@map.id, current_user.id)
|
||||
if star
|
||||
star.delete
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: { message: 'Successfully unstarred map' }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# POST maps/:id/upload_screenshot
|
||||
def screenshot
|
||||
@map = Map.find(params[:id])
|
||||
authorize @map
|
||||
|
||||
png = Base64.decode64(params[:encoded_image]['data:image/png;base64,'.length..-1])
|
||||
StringIO.open(png) do |data|
|
||||
data.class.class_eval { attr_accessor :original_filename, :content_type }
|
||||
data.original_filename = 'map-' + @map.id.to_s + '-screenshot.png'
|
||||
data.content_type = 'image/png'
|
||||
@map.screenshot = data
|
||||
end
|
||||
|
||||
if @map.save
|
||||
render json: { message: 'Successfully uploaded the map screenshot.' }
|
||||
else
|
||||
render json: { message: 'Failed to upload image.' }
|
||||
end
|
||||
end
|
||||
|
||||
# DELETE maps/:id
|
||||
def destroy
|
||||
@map = Map.find(params[:id])
|
||||
authorize @map
|
||||
|
||||
@map.delete
|
||||
|
||||
respond_to do |format|
|
||||
|
@ -366,10 +80,73 @@ class MapsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
# GET maps/:id/contains
|
||||
def contains
|
||||
respond_to do |format|
|
||||
format.json { render json: @map.contains(current_user).as_json(user: current_user) }
|
||||
end
|
||||
end
|
||||
|
||||
# GET maps/:id/export
|
||||
def export
|
||||
exporter = MapExportService.new(current_user, @map)
|
||||
respond_to do |format|
|
||||
format.json { render json: exporter.json }
|
||||
format.csv { send_data exporter.csv }
|
||||
end
|
||||
end
|
||||
|
||||
# POST maps/:id/events/:event
|
||||
def events
|
||||
valid_event = false
|
||||
if params[:event] == 'conversation'
|
||||
Events::ConversationStartedOnMap.publish!(@map, current_user)
|
||||
valid_event = true
|
||||
elsif params[:event] == 'user_presence'
|
||||
Events::UserPresentOnMap.publish!(@map, current_user)
|
||||
valid_event = true
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
head :bad_request unless valid_event
|
||||
head :ok
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Never trust parameters from the scary internet, only allow the white list through.
|
||||
def map_params
|
||||
params.require(:map).permit(:id, :name, :arranged, :desc, :permission)
|
||||
def set_map
|
||||
@map = Map.find(params[:id])
|
||||
authorize @map
|
||||
end
|
||||
|
||||
def create_map_params
|
||||
params.permit(:name, :desc, :permission)
|
||||
end
|
||||
|
||||
def update_map_params
|
||||
params.require(:map).permit(:id, :name, :arranged, :desc, :permission, :screenshot)
|
||||
end
|
||||
|
||||
def create_topics!
|
||||
params[:topicsToMap].split(',').each do |topic|
|
||||
topic = topic.split('/')
|
||||
mapping = Mapping.new(map: @map, user: current_user,
|
||||
mappable: Topic.find(topic[0]),
|
||||
xloc: topic[1], yloc: topic[2])
|
||||
authorize mapping, :create?
|
||||
mapping.save
|
||||
end
|
||||
end
|
||||
|
||||
def create_synapses!
|
||||
params[:synapsesToMap].split(',').each do |synapse_id|
|
||||
mapping = Mapping.new(map: @map, user: current_user,
|
||||
mappable: Synapse.find(synapse_id))
|
||||
authorize mapping, :create?
|
||||
mapping.save
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# frozen_string_literal: true
|
||||
class MessagesController < ApplicationController
|
||||
before_action :require_user, except: [:show]
|
||||
after_action :verify_authorized
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# frozen_string_literal: true
|
||||
class MetacodeSetsController < ApplicationController
|
||||
before_action :require_admin
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
class MetacodesController < ApplicationController
|
||||
before_action :require_admin, except: [:index, :show]
|
||||
before_action :set_metacode, only: [:edit, :update]
|
||||
|
||||
# GET /metacodes
|
||||
# GET /metacodes.json
|
||||
|
@ -8,10 +10,7 @@ class MetacodesController < ApplicationController
|
|||
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
unless authenticated? && user.admin
|
||||
redirect_to root_url, notice: 'You need to be an admin for that.'
|
||||
return false
|
||||
end
|
||||
return unless require_admin
|
||||
render :index
|
||||
end
|
||||
format.json { render json: @metacodes }
|
||||
|
@ -23,7 +22,7 @@ class MetacodesController < ApplicationController
|
|||
# GET /metacodes/action.json
|
||||
def show
|
||||
@metacode = Metacode.where('DOWNCASE(name) = ?', downcase(params[:name])).first if params[:name]
|
||||
@metacode = Metacode.find(params[:id]) unless @metacode
|
||||
set_metacode unless @metacode
|
||||
|
||||
respond_to do |format|
|
||||
format.json { render json: @metacode }
|
||||
|
@ -36,14 +35,13 @@ class MetacodesController < ApplicationController
|
|||
@metacode = Metacode.new
|
||||
|
||||
respond_to do |format|
|
||||
format.html # new.html.erb
|
||||
format.html
|
||||
format.json { render json: @metacode }
|
||||
end
|
||||
end
|
||||
|
||||
# GET /metacodes/1/edit
|
||||
def edit
|
||||
@metacode = Metacode.find(params[:id])
|
||||
end
|
||||
|
||||
# POST /metacodes
|
||||
|
@ -65,8 +63,6 @@ class MetacodesController < ApplicationController
|
|||
# PUT /metacodes/1
|
||||
# PUT /metacodes/1.json
|
||||
def update
|
||||
@metacode = Metacode.find(params[:id])
|
||||
|
||||
respond_to do |format|
|
||||
if @metacode.update(metacode_params)
|
||||
format.html { redirect_to metacodes_url, notice: 'Metacode was successfully updated.' }
|
||||
|
@ -84,4 +80,8 @@ class MetacodesController < ApplicationController
|
|||
def metacode_params
|
||||
params.require(:metacode).permit(:id, :name, :aws_icon, :manual_icon, :color)
|
||||
end
|
||||
|
||||
def set_metacode
|
||||
@metacode = Metacode.find(params[:id])
|
||||
end
|
||||
end
|
||||
|
|
162
app/controllers/search_controller.rb
Normal file
|
@ -0,0 +1,162 @@
|
|||
# frozen_string_literal: true
|
||||
class SearchController < ApplicationController
|
||||
include TopicsHelper
|
||||
include MapsHelper
|
||||
include UsersHelper
|
||||
include SynapsesHelper
|
||||
|
||||
before_action :authorize_search
|
||||
after_action :verify_authorized
|
||||
after_action :verify_policy_scoped, only: [:maps, :mappers, :synapses, :topics]
|
||||
|
||||
# get /search/topics?term=SOMETERM
|
||||
def topics
|
||||
term = params[:term]
|
||||
user = params[:user] ? params[:user] : false
|
||||
|
||||
if term && !term.empty? && term.downcase[0..3] != 'map:' && term.downcase[0..6] != 'mapper:' && !term.casecmp('topic:').zero?
|
||||
|
||||
# remove "topic:" if appended at beginning
|
||||
term = term[6..-1] if term.downcase[0..5] == 'topic:'
|
||||
|
||||
# if desc: search desc instead
|
||||
desc = false
|
||||
if term.downcase[0..4] == 'desc:'
|
||||
term = term[5..-1]
|
||||
desc = true
|
||||
end
|
||||
|
||||
# if link: search link instead
|
||||
link = false
|
||||
if term.downcase[0..4] == 'link:'
|
||||
term = term[5..-1]
|
||||
link = true
|
||||
end
|
||||
|
||||
# check whether there's a filter by metacode as part of the query
|
||||
filterByMetacode = false
|
||||
Metacode.all.each do |m|
|
||||
lOne = m.name.length + 1
|
||||
lTwo = m.name.length
|
||||
|
||||
if term.downcase[0..lTwo] == m.name.downcase + ':'
|
||||
term = term[lOne..-1]
|
||||
filterByMetacode = m
|
||||
end
|
||||
end
|
||||
|
||||
search = '%' + term.downcase + '%'
|
||||
builder = policy_scope(Topic)
|
||||
|
||||
if filterByMetacode
|
||||
if term == ''
|
||||
builder = builder.none
|
||||
else
|
||||
builder = builder.where('LOWER("name") like ? OR
|
||||
LOWER("desc") like ? OR
|
||||
LOWER("link") like ?', search, search, search)
|
||||
builder = builder.where(metacode_id: filterByMetacode.id)
|
||||
end
|
||||
elsif desc
|
||||
builder = builder.where('LOWER("desc") like ?', search)
|
||||
elsif link
|
||||
builder = builder.where('LOWER("link") like ?', search)
|
||||
else # regular case, just search the name
|
||||
builder = builder.where('LOWER("name") like ? OR
|
||||
LOWER("desc") like ? OR
|
||||
LOWER("link") like ?', search, search, search)
|
||||
end
|
||||
|
||||
builder = builder.where(user: user) if user
|
||||
@topics = builder.order(:name)
|
||||
else
|
||||
@topics = []
|
||||
end
|
||||
|
||||
render json: autocomplete_array_json(@topics).to_json
|
||||
end
|
||||
|
||||
# get /search/maps?term=SOMETERM
|
||||
def maps
|
||||
term = params[:term]
|
||||
user = params[:user] ? params[:user] : nil
|
||||
|
||||
if term && !term.empty? && term.downcase[0..5] != 'topic:' && term.downcase[0..6] != 'mapper:' && !term.casecmp('map:').zero?
|
||||
|
||||
# remove "map:" if appended at beginning
|
||||
term = term[4..-1] if term.downcase[0..3] == 'map:'
|
||||
|
||||
# if desc: search desc instead
|
||||
desc = false
|
||||
if term.downcase[0..4] == 'desc:'
|
||||
term = term[5..-1]
|
||||
desc = true
|
||||
end
|
||||
|
||||
search = '%' + term.downcase + '%'
|
||||
builder = policy_scope(Map)
|
||||
|
||||
builder = if desc
|
||||
builder.where('LOWER("desc") like ?', search)
|
||||
else
|
||||
builder.where('LOWER("name") like ?', search)
|
||||
end
|
||||
builder = builder.where(user: user) if user
|
||||
@maps = builder.order(:name)
|
||||
else
|
||||
@maps = []
|
||||
end
|
||||
|
||||
render json: autocomplete_map_array_json(@maps).to_json
|
||||
end
|
||||
|
||||
# get /search/mappers?term=SOMETERM
|
||||
def mappers
|
||||
term = params[:term]
|
||||
if term && !term.empty? && term.downcase[0..3] != 'map:' && term.downcase[0..5] != 'topic:' && !term.casecmp('mapper:').zero?
|
||||
|
||||
# remove "mapper:" if appended at beginning
|
||||
term = term[7..-1] if term.downcase[0..6] == 'mapper:'
|
||||
search = term.downcase + '%'
|
||||
|
||||
skip_policy_scope # TODO: builder = policy_scope(User)
|
||||
builder = User.where('LOWER("name") like ?', search)
|
||||
@mappers = builder.order(:name)
|
||||
else
|
||||
@mappers = []
|
||||
end
|
||||
render json: autocomplete_user_array_json(@mappers).to_json
|
||||
end
|
||||
|
||||
# get /search/synapses?term=SOMETERM OR
|
||||
# get /search/synapses?topic1id=SOMEID&topic2id=SOMEID
|
||||
def synapses
|
||||
term = params[:term]
|
||||
topic1id = params[:topic1id]
|
||||
topic2id = params[:topic2id]
|
||||
|
||||
if term && !term.empty?
|
||||
@synapses = policy_scope(Synapse).where('LOWER("desc") like ?', '%' + term.downcase + '%').order('"desc"')
|
||||
|
||||
@synapses = @synapses.uniq(&:desc)
|
||||
elsif topic1id && !topic1id.empty?
|
||||
@one = policy_scope(Synapse).where(topic1_id: topic1id, topic2_id: topic2id)
|
||||
@two = policy_scope(Synapse).where(topic2_id: topic1id, topic1_id: topic2id)
|
||||
@synapses = @one + @two
|
||||
@synapses.sort! { |s1, s2| s1.desc <=> s2.desc }.to_a
|
||||
else
|
||||
@synapses = []
|
||||
end
|
||||
|
||||
# limit to 5 results
|
||||
@synapses = @synapses.to_a.slice(0, 5)
|
||||
|
||||
render json: autocomplete_synapse_array_json(@synapses).to_json
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def authorize_search
|
||||
authorize :Search
|
||||
end
|
||||
end
|
37
app/controllers/stars_controller.rb
Normal file
|
@ -0,0 +1,37 @@
|
|||
# frozen_string_literal: true
|
||||
class StarsController < ApplicationController
|
||||
before_action :require_user
|
||||
before_action :set_map
|
||||
after_action :verify_authorized
|
||||
|
||||
# POST maps/:id/star
|
||||
def create
|
||||
authorize @map, :star?
|
||||
Star.find_or_create_by(map_id: @map.id, user_id: current_user.id)
|
||||
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: { message: 'Successfully starred map' }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# POST maps/:id/unstar
|
||||
def destroy
|
||||
authorize @map, :unstar?
|
||||
star = Star.find_by(map_id: @map.id, user_id: current_user.id)
|
||||
star&.delete
|
||||
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: { message: 'Successfully unstarred map' }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_map
|
||||
@map = Map.find(params[:id])
|
||||
end
|
||||
end
|
|
@ -1,3 +1,4 @@
|
|||
# frozen_string_literal: true
|
||||
class SynapsesController < ApplicationController
|
||||
include TopicsHelper
|
||||
|
||||
|
@ -21,10 +22,18 @@ class SynapsesController < ApplicationController
|
|||
@synapse = Synapse.new(synapse_params)
|
||||
@synapse.desc = '' if @synapse.desc.nil?
|
||||
@synapse.desc.strip! # no trailing/leading whitespace
|
||||
authorize @synapse
|
||||
|
||||
# we want invalid params to return :unprocessable_entity
|
||||
# so we have to authorize AFTER saving. But if authorize
|
||||
# fails, we need to rollback the SQL transaction
|
||||
success = nil
|
||||
ActiveRecord::Base.transaction do
|
||||
success = @synapse.save
|
||||
success ? authorize(@synapse) : skip_authorization
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
if @synapse.save
|
||||
if success
|
||||
format.json { render json: @synapse, status: :created }
|
||||
else
|
||||
format.json { render json: @synapse.errors, status: :unprocessable_entity }
|
||||
|
@ -62,6 +71,6 @@ class SynapsesController < ApplicationController
|
|||
private
|
||||
|
||||
def synapse_params
|
||||
params.require(:synapse).permit(:id, :desc, :category, :weight, :permission, :node1_id, :node2_id, :user_id)
|
||||
params.require(:synapse).permit(:id, :desc, :category, :weight, :permission, :topic1_id, :topic2_id, :user_id)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# frozen_string_literal: true
|
||||
class TopicsController < ApplicationController
|
||||
include TopicsHelper
|
||||
|
||||
|
@ -9,12 +10,19 @@ class TopicsController < ApplicationController
|
|||
# GET /topics/autocomplete_topic
|
||||
def autocomplete_topic
|
||||
term = params[:term]
|
||||
@topics = if term && !term.empty?
|
||||
policy_scope(Topic.where('LOWER("name") like ?', term.downcase + '%')).order('"name"')
|
||||
else
|
||||
[]
|
||||
end
|
||||
render json: autocomplete_array_json(@topics)
|
||||
if term && !term.empty?
|
||||
@topics = policy_scope(Topic).where('LOWER("name") like ?', term.downcase + '%').order('"name"')
|
||||
@mapTopics = @topics.select { |t| t.metacode.name == 'Metamap' }
|
||||
# prioritize topics which point to maps, over maps
|
||||
@exclude = @mapTopics.length > 0 ? @mapTopics.map(&:name) : ['']
|
||||
@maps = policy_scope(Map).where('LOWER("name") like ? AND name NOT IN (?)', term.downcase + '%', @exclude).order('"name"')
|
||||
else
|
||||
@topics = []
|
||||
@maps = []
|
||||
end
|
||||
@all= @topics.to_a.concat(@maps.to_a).sort { |a, b| a.name <=> b.name }
|
||||
|
||||
render json: autocomplete_array_json(@all).to_json
|
||||
end
|
||||
|
||||
# GET topics/:id
|
||||
|
@ -31,7 +39,7 @@ class TopicsController < ApplicationController
|
|||
|
||||
respond_with(@allsynapses, @alltopics, @allcreators, @topic)
|
||||
end
|
||||
format.json { render json: @topic }
|
||||
format.json { render json: @topic.as_json(user: current_user).to_json }
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -47,9 +55,9 @@ class TopicsController < ApplicationController
|
|||
@allcreators += @allsynapses.map(&:user).uniq
|
||||
|
||||
@json = {}
|
||||
@json['topic'] = @topic
|
||||
@json['topic'] = @topic.as_json(user: current_user)
|
||||
@json['creators'] = @allcreators
|
||||
@json['relatives'] = @alltopics
|
||||
@json['relatives'] = @alltopics.as_json(user: current_user)
|
||||
@json['synapses'] = @allsynapses
|
||||
|
||||
respond_to do |format|
|
||||
|
@ -64,13 +72,14 @@ class TopicsController < ApplicationController
|
|||
|
||||
topicsAlreadyHas = params[:network] ? params[:network].split(',').map(&:to_i) : []
|
||||
|
||||
@alltopics = policy_scope(Topic.relatives(@topic.id, current_user)).to_a
|
||||
@alltopics.delete_if do |topic|
|
||||
alltopics = policy_scope(Topic.relatives(@topic.id, current_user)).to_a
|
||||
alltopics.delete_if { |topic| topic.metacode_id != params[:metacode].to_i } if params[:metacode].present?
|
||||
alltopics.delete_if do |topic|
|
||||
!topicsAlreadyHas.index(topic.id).nil?
|
||||
end
|
||||
|
||||
@json = Hash.new(0)
|
||||
@alltopics.each do |t|
|
||||
alltopics.each do |t|
|
||||
@json[t.metacode.id] += 1
|
||||
end
|
||||
|
||||
|
@ -86,14 +95,15 @@ class TopicsController < ApplicationController
|
|||
|
||||
topicsAlreadyHas = params[:network] ? params[:network].split(',').map(&:to_i) : []
|
||||
|
||||
alltopics = policy_scope(Topic.relatives(@topic.id)).to_a
|
||||
alltopics = policy_scope(Topic.relatives(@topic.id, current_user)).to_a
|
||||
alltopics.delete_if { |topic| topic.metacode_id != params[:metacode].to_i } if params[:metacode].present?
|
||||
alltopics.delete_if do |topic|
|
||||
!topicsAlreadyHas.index(topic.id.to_s).nil?
|
||||
end
|
||||
|
||||
# find synapses between topics in alltopics array
|
||||
allsynapses = policy_scope(Synapse.for_topic(@topic.id)).to_a
|
||||
synapse_ids = (allsynapses.map(&:node1_id) + allsynapses.map(&:node2_id)).uniq
|
||||
synapse_ids = (allsynapses.map(&:topic1_id) + allsynapses.map(&:topic2_id)).uniq
|
||||
allsynapses.delete_if do |synapse|
|
||||
!synapse_ids.index(synapse.id).nil?
|
||||
end
|
||||
|
@ -104,7 +114,7 @@ class TopicsController < ApplicationController
|
|||
end
|
||||
|
||||
@json = {}
|
||||
@json['topics'] = alltopics
|
||||
@json['topics'] = alltopics.as_json(user: current_user)
|
||||
@json['synapses'] = allsynapses
|
||||
@json['creators'] = allcreators
|
||||
|
||||
|
@ -121,7 +131,7 @@ class TopicsController < ApplicationController
|
|||
|
||||
respond_to do |format|
|
||||
if @topic.save
|
||||
format.json { render json: @topic, status: :created }
|
||||
format.json { render json: @topic.as_json(user: current_user), status: :created }
|
||||
else
|
||||
format.json { render json: @topic.errors, status: :unprocessable_entity }
|
||||
end
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
class Users::PasswordsController < Devise::PasswordsController
|
||||
protected
|
||||
|
||||
def after_resetting_password_path_for(resource)
|
||||
signed_in_root_path(resource)
|
||||
end
|
||||
def after_resetting_password_path_for(resource)
|
||||
signed_in_root_path(resource)
|
||||
end
|
||||
|
||||
def after_sending_reset_password_instructions_path_for(_resource_name)
|
||||
sign_in_path if is_navigational_format?
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,25 +1,40 @@
|
|||
# frozen_string_literal: true
|
||||
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.for(:sign_up) << [:name, :joinedwithcode]
|
||||
devise_parameter_sanitizer.permit(:sign_up, keys: [:name, :joinedwithcode])
|
||||
end
|
||||
|
||||
def configure_account_update_params
|
||||
puts devise_parameter_sanitizer_for(:account_update)
|
||||
devise_parameter_sanitizer.for(:account_update) << [:image]
|
||||
devise_parameter_sanitizer.permit(:account_update, keys: [:image])
|
||||
end
|
||||
end
|
||||
|
|
14
app/controllers/users/sessions_controller.rb
Normal file
|
@ -0,0 +1,14 @@
|
|||
class Users::SessionsController < Devise::SessionsController
|
||||
protected
|
||||
|
||||
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
|
||||
end
|
|
@ -1,3 +1,4 @@
|
|||
# frozen_string_literal: true
|
||||
class UsersController < ApplicationController
|
||||
before_action :require_user, only: [:edit, :update, :updatemetacodes]
|
||||
|
||||
|
|
|
@ -1,14 +1,24 @@
|
|||
# frozen_string_literal: true
|
||||
module ApplicationHelper
|
||||
def get_metacodeset
|
||||
@m = current_user.settings.metacodes
|
||||
set = @m[0].include?('metacodeset') ? MetacodeSet.find(@m[0].sub('metacodeset-', '').to_i) : false
|
||||
set
|
||||
def metacodeset
|
||||
metacodes = current_user.settings.metacodes
|
||||
return false unless metacodes[0].include?('metacodeset')
|
||||
if metacodes[0].sub('metacodeset-', '') == 'Most'
|
||||
return 'Most'
|
||||
elsif metacodes[0].sub('metacodeset-', '') == 'Recent'
|
||||
return 'Recent'
|
||||
end
|
||||
MetacodeSet.find(metacodes[0].sub('metacodeset-', '').to_i)
|
||||
end
|
||||
|
||||
def user_metacodes
|
||||
@m = current_user.settings.metacodes
|
||||
set = get_metacodeset
|
||||
@metacodes = if set
|
||||
set = metacodeset
|
||||
@metacodes = if set && set == 'Most'
|
||||
Metacode.where(id: current_user.mostUsedMetacodes).to_a
|
||||
elsif set && set == 'Recent'
|
||||
Metacode.where(id: current_user.recentMetacodes).to_a
|
||||
elsif set
|
||||
set.metacodes.to_a
|
||||
else
|
||||
Metacode.where(id: @m).to_a
|
||||
|
@ -16,7 +26,15 @@ module ApplicationHelper
|
|||
@metacodes.sort! { |m1, m2| m2.name.downcase <=> m1.name.downcase }.rotate!(-1)
|
||||
end
|
||||
|
||||
def determine_invite_link
|
||||
def user_most_used_metacodes
|
||||
@metacodes = current_user.mostUsedMetacodes.map { |id| Metacode.find(id) }
|
||||
end
|
||||
|
||||
def user_recent_metacodes
|
||||
@metacodes = current_user.recentMetacodes.map { |id| Metacode.find(id) }
|
||||
end
|
||||
|
||||
def invite_link
|
||||
"#{request.base_url}/join" + (current_user ? "?code=#{current_user.code}" : '')
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# frozen_string_literal: true
|
||||
module ContentHelper
|
||||
def resource_name
|
||||
:user
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# frozen_string_literal: true
|
||||
module DeviseHelper
|
||||
def devise_error_messages!
|
||||
resource.errors.to_a[0]
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
# frozen_string_literal: true
|
||||
module InMetacodeSetsHelper
|
||||
end
|
||||
|
|