Merge branch 'develop' into feature/convo.algo
This commit is contained in:
commit
72fd2717b6
104 changed files with 2007 additions and 979 deletions
|
@ -6,7 +6,7 @@ export DB_USERNAME='postgres'
|
||||||
export DB_PASSWORD='3112'
|
export DB_PASSWORD='3112'
|
||||||
export DB_HOST='localhost'
|
export DB_HOST='localhost'
|
||||||
export DB_PORT='5432'
|
export DB_PORT='5432'
|
||||||
export DB_NAME='metamap002'
|
export DB_NAME='metamaps'
|
||||||
|
|
||||||
export REALTIME_SERVER='http://localhost:5000'
|
export REALTIME_SERVER='http://localhost:5000'
|
||||||
export MAILER_DEFAULT_URL='localhost:3000'
|
export MAILER_DEFAULT_URL='localhost:3000'
|
||||||
|
|
8
.github/ISSUE_TEMPLATE.md
vendored
8
.github/ISSUE_TEMPLATE.md
vendored
|
@ -1,4 +1,12 @@
|
||||||
|
please link to related trello cards, if they exist, from the following two boards respectively
|
||||||
|
|
||||||
|
https://trello.com/b/8HlCikOX/metamaps-design
|
||||||
|
|
||||||
|
https://trello.com/b/uFOA6a2x/metamaps-feedback-feature-ideas-requests
|
||||||
|
|
||||||
|
[the issue as framed for design]()
|
||||||
|
|
||||||
|
[the issue as framed from the users perspective]()
|
||||||
|
|
||||||
|
|
||||||
============
|
============
|
||||||
|
|
|
@ -19,3 +19,6 @@ Metrics/AbcSize:
|
||||||
|
|
||||||
Style/Documentation:
|
Style/Documentation:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
|
Style/EmptyMethod:
|
||||||
|
EnforcedStyle: expanded
|
||||||
|
|
8
Gemfile
8
Gemfile
|
@ -9,7 +9,6 @@ gem 'aws-sdk'
|
||||||
gem 'best_in_place'
|
gem 'best_in_place'
|
||||||
gem 'delayed_job'
|
gem 'delayed_job'
|
||||||
gem 'delayed_job_active_record'
|
gem 'delayed_job_active_record'
|
||||||
gem 'sucker_punch'
|
|
||||||
gem 'devise'
|
gem 'devise'
|
||||||
gem 'doorkeeper'
|
gem 'doorkeeper'
|
||||||
gem 'dotenv-rails'
|
gem 'dotenv-rails'
|
||||||
|
@ -20,6 +19,7 @@ gem 'kaminari'
|
||||||
gem 'mailboxer'
|
gem 'mailboxer'
|
||||||
gem 'paperclip'
|
gem 'paperclip'
|
||||||
gem 'pg'
|
gem 'pg'
|
||||||
|
gem 'puma'
|
||||||
gem 'pundit'
|
gem 'pundit'
|
||||||
gem 'pundit_extra'
|
gem 'pundit_extra'
|
||||||
gem 'rack-attack'
|
gem 'rack-attack'
|
||||||
|
@ -27,7 +27,7 @@ gem 'rack-cors'
|
||||||
gem 'redis'
|
gem 'redis'
|
||||||
gem 'slack-notifier'
|
gem 'slack-notifier'
|
||||||
gem 'snorlax'
|
gem 'snorlax'
|
||||||
gem 'puma'
|
gem 'sucker_punch'
|
||||||
|
|
||||||
# asset stuff
|
# asset stuff
|
||||||
gem 'jquery-rails'
|
gem 'jquery-rails'
|
||||||
|
@ -36,12 +36,12 @@ gem 'sass-rails'
|
||||||
gem 'uglifier'
|
gem 'uglifier'
|
||||||
|
|
||||||
group :test do
|
group :test do
|
||||||
|
gem 'brakeman', require: false
|
||||||
gem 'factory_girl_rails'
|
gem 'factory_girl_rails'
|
||||||
gem 'json-schema'
|
gem 'json-schema'
|
||||||
gem 'rspec-rails'
|
gem 'rspec-rails'
|
||||||
gem 'shoulda-matchers'
|
gem 'shoulda-matchers'
|
||||||
gem 'simplecov', require: false
|
gem 'simplecov', require: false
|
||||||
gem 'brakeman', require: false
|
|
||||||
end
|
end
|
||||||
|
|
||||||
group :development, :test do
|
group :development, :test do
|
||||||
|
@ -49,6 +49,6 @@ group :development, :test do
|
||||||
gem 'binding_of_caller'
|
gem 'binding_of_caller'
|
||||||
gem 'pry-byebug'
|
gem 'pry-byebug'
|
||||||
gem 'pry-rails'
|
gem 'pry-rails'
|
||||||
gem 'tunemygc'
|
|
||||||
gem 'rubocop'
|
gem 'rubocop'
|
||||||
|
gem 'tunemygc'
|
||||||
end
|
end
|
||||||
|
|
201
Gemfile.lock
201
Gemfile.lock
|
@ -1,57 +1,60 @@
|
||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
actioncable (5.0.0.1)
|
actioncable (5.0.1)
|
||||||
actionpack (= 5.0.0.1)
|
actionpack (= 5.0.1)
|
||||||
nio4r (~> 1.2)
|
nio4r (~> 1.2)
|
||||||
websocket-driver (~> 0.6.1)
|
websocket-driver (~> 0.6.1)
|
||||||
actionmailer (5.0.0.1)
|
actionmailer (5.0.1)
|
||||||
actionpack (= 5.0.0.1)
|
actionpack (= 5.0.1)
|
||||||
actionview (= 5.0.0.1)
|
actionview (= 5.0.1)
|
||||||
activejob (= 5.0.0.1)
|
activejob (= 5.0.1)
|
||||||
mail (~> 2.5, >= 2.5.4)
|
mail (~> 2.5, >= 2.5.4)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
actionpack (5.0.0.1)
|
actionpack (5.0.1)
|
||||||
actionview (= 5.0.0.1)
|
actionview (= 5.0.1)
|
||||||
activesupport (= 5.0.0.1)
|
activesupport (= 5.0.1)
|
||||||
rack (~> 2.0)
|
rack (~> 2.0)
|
||||||
rack-test (~> 0.6.3)
|
rack-test (~> 0.6.3)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||||
actionview (5.0.0.1)
|
actionview (5.0.1)
|
||||||
activesupport (= 5.0.0.1)
|
activesupport (= 5.0.1)
|
||||||
builder (~> 3.1)
|
builder (~> 3.1)
|
||||||
erubis (~> 2.7.0)
|
erubis (~> 2.7.0)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||||
active_model_serializers (0.10.2)
|
active_model_serializers (0.10.4)
|
||||||
actionpack (>= 4.1, < 6)
|
actionpack (>= 4.1, < 6)
|
||||||
activemodel (>= 4.1, < 6)
|
activemodel (>= 4.1, < 6)
|
||||||
jsonapi (~> 0.1.1.beta2)
|
case_transform (>= 0.2)
|
||||||
railties (>= 4.1, < 6)
|
jsonapi (= 0.1.1.beta6)
|
||||||
activejob (5.0.0.1)
|
activejob (5.0.1)
|
||||||
activesupport (= 5.0.0.1)
|
activesupport (= 5.0.1)
|
||||||
globalid (>= 0.3.6)
|
globalid (>= 0.3.6)
|
||||||
activemodel (5.0.0.1)
|
activemodel (5.0.1)
|
||||||
activesupport (= 5.0.0.1)
|
activesupport (= 5.0.1)
|
||||||
activerecord (5.0.0.1)
|
activerecord (5.0.1)
|
||||||
activemodel (= 5.0.0.1)
|
activemodel (= 5.0.1)
|
||||||
activesupport (= 5.0.0.1)
|
activesupport (= 5.0.1)
|
||||||
arel (~> 7.0)
|
arel (~> 7.0)
|
||||||
activesupport (5.0.0.1)
|
activesupport (5.0.1)
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
i18n (~> 0.7)
|
i18n (~> 0.7)
|
||||||
minitest (~> 5.1)
|
minitest (~> 5.1)
|
||||||
tzinfo (~> 1.1)
|
tzinfo (~> 1.1)
|
||||||
addressable (2.3.8)
|
addressable (2.5.0)
|
||||||
arel (7.1.2)
|
public_suffix (~> 2.0, >= 2.0.2)
|
||||||
|
arel (7.1.4)
|
||||||
ast (2.3.0)
|
ast (2.3.0)
|
||||||
aws-sdk (2.6.3)
|
aws-sdk (2.7.0)
|
||||||
aws-sdk-resources (= 2.6.3)
|
aws-sdk-resources (= 2.7.0)
|
||||||
aws-sdk-core (2.6.3)
|
aws-sdk-core (2.7.0)
|
||||||
|
aws-sigv4 (~> 1.0)
|
||||||
jmespath (~> 1.0)
|
jmespath (~> 1.0)
|
||||||
aws-sdk-resources (2.6.3)
|
aws-sdk-resources (2.7.0)
|
||||||
aws-sdk-core (= 2.6.3)
|
aws-sdk-core (= 2.7.0)
|
||||||
|
aws-sigv4 (1.0.0)
|
||||||
bcrypt (3.1.11)
|
bcrypt (3.1.11)
|
||||||
best_in_place (3.1.0)
|
best_in_place (3.1.0)
|
||||||
actionpack (>= 3.2)
|
actionpack (>= 3.2)
|
||||||
|
@ -62,21 +65,20 @@ GEM
|
||||||
rack (>= 0.9.0)
|
rack (>= 0.9.0)
|
||||||
binding_of_caller (0.7.2)
|
binding_of_caller (0.7.2)
|
||||||
debug_inspector (>= 0.0.1)
|
debug_inspector (>= 0.0.1)
|
||||||
brakeman (3.4.0)
|
brakeman (3.4.1)
|
||||||
builder (3.2.2)
|
builder (3.2.3)
|
||||||
byebug (9.0.5)
|
byebug (9.0.6)
|
||||||
carrierwave (0.11.2)
|
carrierwave (1.0.0)
|
||||||
activemodel (>= 3.2.0)
|
activemodel (>= 4.0.0)
|
||||||
activesupport (>= 3.2.0)
|
activesupport (>= 4.0.0)
|
||||||
json (>= 1.7)
|
|
||||||
mime-types (>= 1.16)
|
mime-types (>= 1.16)
|
||||||
mimemagic (>= 0.3.0)
|
case_transform (0.2)
|
||||||
climate_control (0.0.3)
|
activesupport
|
||||||
activesupport (>= 3.0)
|
climate_control (0.1.0)
|
||||||
cocaine (0.5.8)
|
cocaine (0.5.8)
|
||||||
climate_control (>= 0.0.3, < 1.0)
|
climate_control (>= 0.0.3, < 1.0)
|
||||||
coderay (1.1.1)
|
coderay (1.1.1)
|
||||||
concurrent-ruby (1.0.2)
|
concurrent-ruby (1.0.4)
|
||||||
debug_inspector (0.0.2)
|
debug_inspector (0.0.2)
|
||||||
delayed_job (4.1.2)
|
delayed_job (4.1.2)
|
||||||
activesupport (>= 3.0, < 5.1)
|
activesupport (>= 3.0, < 5.1)
|
||||||
|
@ -89,23 +91,23 @@ GEM
|
||||||
railties (>= 4.1.0, < 5.1)
|
railties (>= 4.1.0, < 5.1)
|
||||||
responders
|
responders
|
||||||
warden (~> 1.2.3)
|
warden (~> 1.2.3)
|
||||||
diff-lcs (1.2.5)
|
diff-lcs (1.3)
|
||||||
docile (1.1.5)
|
docile (1.1.5)
|
||||||
doorkeeper (4.2.0)
|
doorkeeper (4.2.0)
|
||||||
railties (>= 4.2)
|
railties (>= 4.2)
|
||||||
dotenv (2.1.1)
|
dotenv (2.1.2)
|
||||||
dotenv-rails (2.1.1)
|
dotenv-rails (2.1.2)
|
||||||
dotenv (= 2.1.1)
|
dotenv (= 2.1.2)
|
||||||
railties (>= 4.0, < 5.1)
|
railties (>= 3.2, < 5.1)
|
||||||
erubis (2.7.0)
|
erubis (2.7.0)
|
||||||
exception_notification (4.2.1)
|
exception_notification (4.2.1)
|
||||||
actionmailer (>= 4.0, < 6)
|
actionmailer (>= 4.0, < 6)
|
||||||
activesupport (>= 4.0, < 6)
|
activesupport (>= 4.0, < 6)
|
||||||
execjs (2.7.0)
|
execjs (2.7.0)
|
||||||
factory_girl (4.7.0)
|
factory_girl (4.8.0)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
factory_girl_rails (4.7.0)
|
factory_girl_rails (4.8.0)
|
||||||
factory_girl (~> 4.7.0)
|
factory_girl (~> 4.8.0)
|
||||||
railties (>= 3.0.0)
|
railties (>= 3.0.0)
|
||||||
globalid (0.3.7)
|
globalid (0.3.7)
|
||||||
activesupport (>= 4.1.0)
|
activesupport (>= 4.1.0)
|
||||||
|
@ -113,20 +115,32 @@ GEM
|
||||||
multi_xml (>= 0.5.2)
|
multi_xml (>= 0.5.2)
|
||||||
i18n (0.7.0)
|
i18n (0.7.0)
|
||||||
jmespath (1.3.1)
|
jmespath (1.3.1)
|
||||||
jquery-rails (4.2.1)
|
jquery-rails (4.2.2)
|
||||||
rails-dom-testing (>= 1, < 3)
|
rails-dom-testing (>= 1, < 3)
|
||||||
railties (>= 4.2.0)
|
railties (>= 4.2.0)
|
||||||
thor (>= 0.14, < 2.0)
|
thor (>= 0.14, < 2.0)
|
||||||
jquery-ui-rails (5.0.5)
|
jquery-ui-rails (6.0.1)
|
||||||
railties (>= 3.2.16)
|
railties (>= 3.2.16)
|
||||||
json (1.8.3)
|
json (2.0.3)
|
||||||
json-schema (2.6.2)
|
json-schema (2.7.0)
|
||||||
addressable (~> 2.3.8)
|
addressable (>= 2.4)
|
||||||
jsonapi (0.1.1.beta2)
|
jsonapi (0.1.1.beta6)
|
||||||
json (~> 1.8)
|
jsonapi-parser (= 0.1.1.beta3)
|
||||||
kaminari (0.17.0)
|
jsonapi-renderer (= 0.1.1.beta1)
|
||||||
actionpack (>= 3.0.0)
|
jsonapi-parser (0.1.1.beta3)
|
||||||
activesupport (>= 3.0.0)
|
jsonapi-renderer (0.1.1.beta1)
|
||||||
|
kaminari (1.0.1)
|
||||||
|
activesupport (>= 4.1.0)
|
||||||
|
kaminari-actionview (= 1.0.1)
|
||||||
|
kaminari-activerecord (= 1.0.1)
|
||||||
|
kaminari-core (= 1.0.1)
|
||||||
|
kaminari-actionview (1.0.1)
|
||||||
|
actionview
|
||||||
|
kaminari-core (= 1.0.1)
|
||||||
|
kaminari-activerecord (1.0.1)
|
||||||
|
activerecord
|
||||||
|
kaminari-core (= 1.0.1)
|
||||||
|
kaminari-core (1.0.1)
|
||||||
loofah (2.0.3)
|
loofah (2.0.3)
|
||||||
nokogiri (>= 1.5.9)
|
nokogiri (>= 1.5.9)
|
||||||
mail (2.6.4)
|
mail (2.6.4)
|
||||||
|
@ -140,12 +154,11 @@ GEM
|
||||||
mime-types-data (3.2016.0521)
|
mime-types-data (3.2016.0521)
|
||||||
mimemagic (0.3.2)
|
mimemagic (0.3.2)
|
||||||
mini_portile2 (2.1.0)
|
mini_portile2 (2.1.0)
|
||||||
minitest (5.9.1)
|
minitest (5.10.1)
|
||||||
multi_xml (0.5.5)
|
multi_xml (0.6.0)
|
||||||
nio4r (1.2.1)
|
nio4r (1.2.1)
|
||||||
nokogiri (1.6.8)
|
nokogiri (1.7.0.1)
|
||||||
mini_portile2 (~> 2.1.0)
|
mini_portile2 (~> 2.1.0)
|
||||||
pkg-config (~> 1.1.7)
|
|
||||||
orm_adapter (0.5.0)
|
orm_adapter (0.5.0)
|
||||||
paperclip (5.1.0)
|
paperclip (5.1.0)
|
||||||
activemodel (>= 4.2.0)
|
activemodel (>= 4.2.0)
|
||||||
|
@ -153,20 +166,20 @@ GEM
|
||||||
cocaine (~> 0.5.5)
|
cocaine (~> 0.5.5)
|
||||||
mime-types
|
mime-types
|
||||||
mimemagic (~> 0.3.0)
|
mimemagic (~> 0.3.0)
|
||||||
parser (2.3.1.4)
|
parser (2.3.3.1)
|
||||||
ast (~> 2.2)
|
ast (~> 2.2)
|
||||||
pg (0.19.0)
|
pg (0.19.0)
|
||||||
pkg-config (1.1.7)
|
|
||||||
powerpack (0.1.1)
|
powerpack (0.1.1)
|
||||||
pry (0.10.4)
|
pry (0.10.4)
|
||||||
coderay (~> 1.1.0)
|
coderay (~> 1.1.0)
|
||||||
method_source (~> 0.8.1)
|
method_source (~> 0.8.1)
|
||||||
slop (~> 3.4)
|
slop (~> 3.4)
|
||||||
pry-byebug (3.4.0)
|
pry-byebug (3.4.2)
|
||||||
byebug (~> 9.0)
|
byebug (~> 9.0)
|
||||||
pry (~> 0.10)
|
pry (~> 0.10)
|
||||||
pry-rails (0.3.4)
|
pry-rails (0.3.4)
|
||||||
pry (>= 0.9.10)
|
pry (>= 0.9.10)
|
||||||
|
public_suffix (2.0.5)
|
||||||
puma (3.6.2)
|
puma (3.6.2)
|
||||||
pundit (1.1.0)
|
pundit (1.1.0)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
|
@ -177,35 +190,35 @@ GEM
|
||||||
rack-cors (0.4.0)
|
rack-cors (0.4.0)
|
||||||
rack-test (0.6.3)
|
rack-test (0.6.3)
|
||||||
rack (>= 1.0)
|
rack (>= 1.0)
|
||||||
rails (5.0.0.1)
|
rails (5.0.1)
|
||||||
actioncable (= 5.0.0.1)
|
actioncable (= 5.0.1)
|
||||||
actionmailer (= 5.0.0.1)
|
actionmailer (= 5.0.1)
|
||||||
actionpack (= 5.0.0.1)
|
actionpack (= 5.0.1)
|
||||||
actionview (= 5.0.0.1)
|
actionview (= 5.0.1)
|
||||||
activejob (= 5.0.0.1)
|
activejob (= 5.0.1)
|
||||||
activemodel (= 5.0.0.1)
|
activemodel (= 5.0.1)
|
||||||
activerecord (= 5.0.0.1)
|
activerecord (= 5.0.1)
|
||||||
activesupport (= 5.0.0.1)
|
activesupport (= 5.0.1)
|
||||||
bundler (>= 1.3.0, < 2.0)
|
bundler (>= 1.3.0, < 2.0)
|
||||||
railties (= 5.0.0.1)
|
railties (= 5.0.1)
|
||||||
sprockets-rails (>= 2.0.0)
|
sprockets-rails (>= 2.0.0)
|
||||||
rails-dom-testing (2.0.1)
|
rails-dom-testing (2.0.2)
|
||||||
activesupport (>= 4.2.0, < 6.0)
|
activesupport (>= 4.2.0, < 6.0)
|
||||||
nokogiri (~> 1.6.0)
|
nokogiri (~> 1.6)
|
||||||
rails-html-sanitizer (1.0.3)
|
rails-html-sanitizer (1.0.3)
|
||||||
loofah (~> 2.0)
|
loofah (~> 2.0)
|
||||||
railties (5.0.0.1)
|
railties (5.0.1)
|
||||||
actionpack (= 5.0.0.1)
|
actionpack (= 5.0.1)
|
||||||
activesupport (= 5.0.0.1)
|
activesupport (= 5.0.1)
|
||||||
method_source
|
method_source
|
||||||
rake (>= 0.8.7)
|
rake (>= 0.8.7)
|
||||||
thor (>= 0.18.1, < 2.0)
|
thor (>= 0.18.1, < 2.0)
|
||||||
rainbow (2.1.0)
|
rainbow (2.2.1)
|
||||||
rake (11.3.0)
|
rake (12.0.0)
|
||||||
redis (3.3.1)
|
redis (3.3.2)
|
||||||
responders (2.3.0)
|
responders (2.3.0)
|
||||||
railties (>= 4.2.0, < 5.1)
|
railties (>= 4.2.0, < 5.1)
|
||||||
rspec-core (3.5.3)
|
rspec-core (3.5.4)
|
||||||
rspec-support (~> 3.5.0)
|
rspec-support (~> 3.5.0)
|
||||||
rspec-expectations (3.5.0)
|
rspec-expectations (3.5.0)
|
||||||
diff-lcs (>= 1.2.0, < 2.0)
|
diff-lcs (>= 1.2.0, < 2.0)
|
||||||
|
@ -222,14 +235,14 @@ GEM
|
||||||
rspec-mocks (~> 3.5.0)
|
rspec-mocks (~> 3.5.0)
|
||||||
rspec-support (~> 3.5.0)
|
rspec-support (~> 3.5.0)
|
||||||
rspec-support (3.5.0)
|
rspec-support (3.5.0)
|
||||||
rubocop (0.43.0)
|
rubocop (0.47.1)
|
||||||
parser (>= 2.3.1.1, < 3.0)
|
parser (>= 2.3.3.1, < 3.0)
|
||||||
powerpack (~> 0.1)
|
powerpack (~> 0.1)
|
||||||
rainbow (>= 1.99.1, < 3.0)
|
rainbow (>= 1.99.1, < 3.0)
|
||||||
ruby-progressbar (~> 1.7)
|
ruby-progressbar (~> 1.7)
|
||||||
unicode-display_width (~> 1.0, >= 1.0.1)
|
unicode-display_width (~> 1.0, >= 1.0.1)
|
||||||
ruby-progressbar (1.8.1)
|
ruby-progressbar (1.8.1)
|
||||||
sass (3.4.22)
|
sass (3.4.23)
|
||||||
sass-rails (5.0.6)
|
sass-rails (5.0.6)
|
||||||
railties (>= 4.0.0, < 6)
|
railties (>= 4.0.0, < 6)
|
||||||
sass (~> 3.1)
|
sass (~> 3.1)
|
||||||
|
@ -243,11 +256,11 @@ GEM
|
||||||
json (>= 1.8, < 3)
|
json (>= 1.8, < 3)
|
||||||
simplecov-html (~> 0.10.0)
|
simplecov-html (~> 0.10.0)
|
||||||
simplecov-html (0.10.0)
|
simplecov-html (0.10.0)
|
||||||
slack-notifier (1.5.1)
|
slack-notifier (2.0.0)
|
||||||
slop (3.6.0)
|
slop (3.6.0)
|
||||||
snorlax (0.1.6)
|
snorlax (0.1.6)
|
||||||
rails (> 4.1)
|
rails (> 4.1)
|
||||||
sprockets (3.7.0)
|
sprockets (3.7.1)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
rack (> 1, < 3)
|
rack (> 1, < 3)
|
||||||
sprockets-rails (3.2.0)
|
sprockets-rails (3.2.0)
|
||||||
|
@ -256,15 +269,15 @@ GEM
|
||||||
sprockets (>= 3.0.0)
|
sprockets (>= 3.0.0)
|
||||||
sucker_punch (2.0.2)
|
sucker_punch (2.0.2)
|
||||||
concurrent-ruby (~> 1.0.0)
|
concurrent-ruby (~> 1.0.0)
|
||||||
thor (0.19.1)
|
thor (0.19.4)
|
||||||
thread_safe (0.3.5)
|
thread_safe (0.3.5)
|
||||||
tilt (2.0.5)
|
tilt (2.0.5)
|
||||||
tunemygc (1.0.68)
|
tunemygc (1.0.69)
|
||||||
tzinfo (1.2.2)
|
tzinfo (1.2.2)
|
||||||
thread_safe (~> 0.1)
|
thread_safe (~> 0.1)
|
||||||
uglifier (3.0.2)
|
uglifier (3.0.4)
|
||||||
execjs (>= 0.3.0, < 3)
|
execjs (>= 0.3.0, < 3)
|
||||||
unicode-display_width (1.1.1)
|
unicode-display_width (1.1.3)
|
||||||
warden (1.2.6)
|
warden (1.2.6)
|
||||||
rack (>= 1.0)
|
rack (>= 1.0)
|
||||||
websocket-driver (0.6.4)
|
websocket-driver (0.6.4)
|
||||||
|
@ -321,4 +334,4 @@ RUBY VERSION
|
||||||
ruby 2.3.0p0
|
ruby 2.3.0p0
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
1.13.6
|
1.13.7
|
||||||
|
|
11
README.md
11
README.md
|
@ -2,6 +2,7 @@ Metamaps
|
||||||
=======
|
=======
|
||||||
|
|
||||||
[](https://travis-ci.org/metamaps/metamaps)
|
[](https://travis-ci.org/metamaps/metamaps)
|
||||||
|
[](https://codeclimate.com/github/metamaps/metamaps)
|
||||||
|
|
||||||
## What is Metamaps?
|
## What is Metamaps?
|
||||||
|
|
||||||
|
@ -16,8 +17,14 @@ Metamaps is developed and maintained by a distributed, nomadic community compris
|
||||||
- Contact: [team@metamaps.cc](mailto:team@metamaps.cc) or [@metamapps](https://twitter.com/metamapps) on Twitter
|
- Contact: [team@metamaps.cc](mailto:team@metamaps.cc) or [@metamapps](https://twitter.com/metamapps) on Twitter
|
||||||
- User Documentation: [docs.metamaps.cc](https://docs.metamaps.cc)
|
- User Documentation: [docs.metamaps.cc](https://docs.metamaps.cc)
|
||||||
- User Community: [hylo.com/c/metamaps](https://www.hylo.com/c/metamaps)
|
- 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 see what we're developing, or to weigh in on what you'd like to see developed, see our [Metamaps Feedback and Features](https://trello.com/b/uFOA6a2x/metamaps-feedback-feature-ideas-requests) board on trello
|
||||||
- To send us a personal message or request an invite to the open beta, get in touch with us via email, Twitter, or Hylo
|
- To follow along with, or contribute,to our design process, see our [Metamaps Design](https://trello.com/b/8HlCikOX/metamaps-design) board on trello
|
||||||
|
- To follow along with, or contribute to, our development process, see our [Github Issues and Pull Requests](https://github.com/metamaps/metamaps/issues)
|
||||||
|
- Request an invite to the open beta [here](https://metamaps.cc/request)
|
||||||
|
|
||||||
|
<!-- markdown hack to split two lists -->
|
||||||
|
|
||||||
|
- To send us a personal message get in touch with us via email, Twitter, or Hylo
|
||||||
- If you would like to report a bug, please check the [issues][contributing-issues] section in our [contributing instructions][contributing].
|
- If you would like to report a bug, please check the [issues][contributing-issues] section in our [contributing instructions][contributing].
|
||||||
- If you would like to get set up as a developer, that's great! Read on for help getting your development environment set up.
|
- If you would like to get set up as a developer, that's great! Read on for help getting your development environment set up.
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// eslint-disable spaced-comment
|
||||||
// JS and CSS bundles
|
// JS and CSS bundles
|
||||||
//= link_directory ../javascripts .js
|
//= link_directory ../javascripts .js
|
||||||
//= link_directory ../stylesheets .css
|
//= link_directory ../stylesheets .css
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
// eslint-disable spaced-comment
|
|
||||||
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
||||||
// listed below.
|
// listed below.
|
||||||
//
|
//
|
||||||
|
@ -10,7 +9,8 @@
|
||||||
//
|
//
|
||||||
// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
|
// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
|
||||||
// GO AFTER THE REQUIRES BELOW.
|
// GO AFTER THE REQUIRES BELOW.
|
||||||
//
|
//
|
||||||
|
/* eslint-disable spaced-comment */
|
||||||
//= require jquery
|
//= require jquery
|
||||||
//= require jquery-ui
|
//= require jquery-ui
|
||||||
//= require jquery_ujs
|
//= require jquery_ujs
|
||||||
|
@ -19,3 +19,4 @@
|
||||||
//= require ./webpacked/metamaps.bundle
|
//= require ./webpacked/metamaps.bundle
|
||||||
//= require ./Metamaps.ServerData
|
//= require ./Metamaps.ServerData
|
||||||
//= require homepageVimeoFallback
|
//= require homepageVimeoFallback
|
||||||
|
/* eslint-enable spaced-comment */
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
/* global $ */
|
/* global $ */
|
||||||
|
|
||||||
$(document).ready(function () {
|
$(document).ready(function() {
|
||||||
if (window.location.pathname === '/') {
|
if (window.location.pathname === '/') {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'GET',
|
type: 'GET',
|
||||||
url: 'https://player.vimeo.com',
|
url: 'https://player.vimeo.com',
|
||||||
error: function (e) {
|
error: function(e) {
|
||||||
$('.homeVideo').hide()
|
$('.homeVideo').hide()
|
||||||
$('.homeVideo').replaceWith($('<video/>', {
|
$('.homeVideo').replaceWith($('<video/>', {
|
||||||
poster: '/assets/metamaps-intro-poster.webp',
|
poster: '/assets/metamaps-intro-poster.webp',
|
||||||
|
|
161
app/assets/javascripts/lib/ajaxq.js
Normal file
161
app/assets/javascripts/lib/ajaxq.js
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
// AjaxQ jQuery Plugin
|
||||||
|
// Copyright (c) 2012 Foliotek Inc.
|
||||||
|
// MIT License
|
||||||
|
// https://github.com/Foliotek/ajaxq
|
||||||
|
// Uses CommonJS, AMD or browser globals to create a jQuery plugin.
|
||||||
|
|
||||||
|
(function (factory) {
|
||||||
|
if (typeof define === 'function' && define.amd) {
|
||||||
|
// AMD. Register as an anonymous module.
|
||||||
|
define(['jquery'], factory);
|
||||||
|
} else if (typeof module === 'object' && module.exports) {
|
||||||
|
// Node/CommonJS
|
||||||
|
module.exports = factory(require('jquery'));
|
||||||
|
} else {
|
||||||
|
// Browser globals
|
||||||
|
factory(jQuery);
|
||||||
|
}
|
||||||
|
}(function ($) {
|
||||||
|
var queues = {};
|
||||||
|
var activeReqs = {};
|
||||||
|
|
||||||
|
// Register an $.ajaxq function, which follows the $.ajax interface, but allows a queue name which will force only one request per queue to fire.
|
||||||
|
// opts can be the regular $.ajax settings plainObject, or a callback returning the settings object, to be evaluated just prior to the actual call to $.ajax.
|
||||||
|
$.ajaxq = function(qname, opts) {
|
||||||
|
|
||||||
|
if (typeof opts === "undefined") {
|
||||||
|
throw ("AjaxQ: queue name is not provided");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Will return a Deferred promise object extended with success/error/callback, so that this function matches the interface of $.ajax
|
||||||
|
var deferred = $.Deferred(),
|
||||||
|
promise = deferred.promise();
|
||||||
|
|
||||||
|
promise.success = promise.done;
|
||||||
|
promise.error = promise.fail;
|
||||||
|
promise.complete = promise.always;
|
||||||
|
|
||||||
|
// Check whether options are to be evaluated at call time or not.
|
||||||
|
var deferredOpts = typeof opts === 'function';
|
||||||
|
// Create a deep copy of the arguments, and enqueue this request.
|
||||||
|
var clonedOptions = !deferredOpts ? $.extend(true, {}, opts) : null;
|
||||||
|
enqueue(function() {
|
||||||
|
// Send off the ajax request now that the item has been removed from the queue
|
||||||
|
var jqXHR = $.ajax.apply(window, [deferredOpts ? opts() : clonedOptions]);
|
||||||
|
|
||||||
|
// Notify the returned deferred object with the correct context when the jqXHR is done or fails
|
||||||
|
// Note that 'always' will automatically be fired once one of these are called: http://api.jquery.com/category/deferred-object/.
|
||||||
|
jqXHR.done(function() {
|
||||||
|
deferred.resolve.apply(this, arguments);
|
||||||
|
});
|
||||||
|
jqXHR.fail(function() {
|
||||||
|
deferred.reject.apply(this, arguments);
|
||||||
|
});
|
||||||
|
|
||||||
|
jqXHR.always(dequeue); // make sure to dequeue the next request AFTER the done and fail callbacks are fired
|
||||||
|
|
||||||
|
return jqXHR;
|
||||||
|
});
|
||||||
|
|
||||||
|
return promise;
|
||||||
|
|
||||||
|
|
||||||
|
// If there is no queue, create an empty one and instantly process this item.
|
||||||
|
// Otherwise, just add this item onto it for later processing.
|
||||||
|
function enqueue(cb) {
|
||||||
|
if (!queues[qname]) {
|
||||||
|
queues[qname] = [];
|
||||||
|
var xhr = cb();
|
||||||
|
activeReqs[qname] = xhr;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
queues[qname].push(cb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the next callback from the queue and fire it off.
|
||||||
|
// If the queue was empty (this was the last item), delete it from memory so the next one can be instantly processed.
|
||||||
|
function dequeue() {
|
||||||
|
if (!queues[qname]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var nextCallback = queues[qname].shift();
|
||||||
|
if (nextCallback) {
|
||||||
|
var xhr = nextCallback();
|
||||||
|
activeReqs[qname] = xhr;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
delete queues[qname];
|
||||||
|
delete activeReqs[qname];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Register a $.postq and $.getq method to provide shortcuts for $.get and $.post
|
||||||
|
// Copied from jQuery source to make sure the functions share the same defaults as $.get and $.post.
|
||||||
|
$.each( [ "getq", "postq" ], function( i, method ) {
|
||||||
|
$[ method ] = function( qname, url, data, callback, type ) {
|
||||||
|
|
||||||
|
if ( $.isFunction( data ) ) {
|
||||||
|
type = type || callback;
|
||||||
|
callback = data;
|
||||||
|
data = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $.ajaxq(qname, {
|
||||||
|
type: method === "postq" ? "post" : "get",
|
||||||
|
url: url,
|
||||||
|
data: data,
|
||||||
|
success: callback,
|
||||||
|
dataType: type
|
||||||
|
});
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
var isQueueRunning = function(qname) {
|
||||||
|
return (queues.hasOwnProperty(qname) && queues[qname].length > 0) || activeReqs.hasOwnProperty(qname);
|
||||||
|
};
|
||||||
|
|
||||||
|
var isAnyQueueRunning = function() {
|
||||||
|
for (var i in queues) {
|
||||||
|
if (isQueueRunning(i)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
$.ajaxq.isRunning = function(qname) {
|
||||||
|
if (qname) return isQueueRunning(qname);
|
||||||
|
else return isAnyQueueRunning();
|
||||||
|
};
|
||||||
|
|
||||||
|
$.ajaxq.getActiveRequest = function(qname) {
|
||||||
|
if (!qname) throw ("AjaxQ: queue name is required");
|
||||||
|
|
||||||
|
return activeReqs[qname];
|
||||||
|
};
|
||||||
|
|
||||||
|
$.ajaxq.abort = function(qname) {
|
||||||
|
if (!qname) throw ("AjaxQ: queue name is required");
|
||||||
|
|
||||||
|
var current = $.ajaxq.getActiveRequest(qname);
|
||||||
|
delete queues[qname];
|
||||||
|
delete activeReqs[qname];
|
||||||
|
if (current) current.abort();
|
||||||
|
};
|
||||||
|
|
||||||
|
$.ajaxq.clear = function(qname) {
|
||||||
|
if (!qname) {
|
||||||
|
for (var i in queues) {
|
||||||
|
if (queues.hasOwnProperty(i)) {
|
||||||
|
queues[i] = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (queues[qname]) {
|
||||||
|
queues[qname] = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}));
|
|
@ -1575,6 +1575,7 @@ h3.filterBox {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
margin: 0.75em;
|
margin: 0.75em;
|
||||||
padding: 0.75em;
|
padding: 0.75em;
|
||||||
|
padding-top: 0.85em;
|
||||||
height: 3em;
|
height: 3em;
|
||||||
background-color: #AAB0FB;
|
background-color: #AAB0FB;
|
||||||
border-radius: 0.3em;
|
border-radius: 0.3em;
|
||||||
|
@ -2029,6 +2030,7 @@ input.collaboratorSearchField {
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: none;
|
display: none;
|
||||||
|
padding: 0 2px;
|
||||||
}
|
}
|
||||||
.mapInfoShareIcon {
|
.mapInfoShareIcon {
|
||||||
width: 24px;
|
width: 24px;
|
||||||
|
@ -2068,6 +2070,43 @@ and it won't be important on password protected instances */
|
||||||
.yourMap .mapInfoDelete {
|
.yourMap .mapInfoDelete {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mapInfoButtonsWrapper .mapInfoThumbnail {
|
||||||
|
display: block;
|
||||||
|
background-image: url(<%= asset_path('screenshot_sprite.png') %>);
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
|
||||||
|
& > span {
|
||||||
|
bottom: -8px;
|
||||||
|
right: 2px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #e0e0e0;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-position: -32px 0;
|
||||||
|
|
||||||
|
.tooltip {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 30px;
|
||||||
|
background: black;
|
||||||
|
color: white;
|
||||||
|
border-radius: 2px;
|
||||||
|
padding: 3px 5px 2px 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.mapInfoButtonsWrapper span {
|
.mapInfoButtonsWrapper span {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
263
app/assets/stylesheets/emoji-mart-0.3.5.css
Normal file
263
app/assets/stylesheets/emoji-mart-0.3.5.css
Normal file
|
@ -0,0 +1,263 @@
|
||||||
|
.emoji-mart,
|
||||||
|
.emoji-mart * {
|
||||||
|
box-sizing: border-box;
|
||||||
|
line-height: 1.15;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-mart {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", sans-serif;
|
||||||
|
font-size: 16px;
|
||||||
|
display: inline-block;
|
||||||
|
color: #222427;
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
border-radius: 5px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-mart .emoji-mart-emoji {
|
||||||
|
padding: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-mart-bar:first-child {
|
||||||
|
border-top-left-radius: 5px;
|
||||||
|
border-top-right-radius: 5px;
|
||||||
|
}
|
||||||
|
.emoji-mart-bar:last-child {
|
||||||
|
border-bottom-left-radius: 5px;
|
||||||
|
border-bottom-right-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-mart-anchors {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0 6px;
|
||||||
|
color: #858585;
|
||||||
|
line-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-mart-anchor {
|
||||||
|
position: relative;
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
padding: 12px 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: color .1s ease-out;
|
||||||
|
}
|
||||||
|
.emoji-mart-anchor:hover,
|
||||||
|
.emoji-mart-anchor-selected {
|
||||||
|
color: #464646;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-mart-anchor-selected .emoji-mart-anchor-bar {
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-mart-anchor-bar {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -3px; left: 0;
|
||||||
|
width: 100%; height: 3px;
|
||||||
|
background-color: #464646;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-mart-anchors i {
|
||||||
|
display: inline-block;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-mart-anchors svg {
|
||||||
|
fill: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-mart-scroll {
|
||||||
|
overflow-y: scroll;
|
||||||
|
height: 270px;
|
||||||
|
padding: 0 6px 6px 6px;
|
||||||
|
border: solid #d9d9d9;
|
||||||
|
border-width: 1px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-mart-search {
|
||||||
|
font-size: 16px;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
padding: .2em .6em;
|
||||||
|
margin-top: 6px;
|
||||||
|
border-radius: 25px;
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-mart-category .emoji-mart-emoji span {
|
||||||
|
z-index: 1;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-mart-category .emoji-mart-emoji:hover:before {
|
||||||
|
z-index: 0;
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0; left: 0;
|
||||||
|
width: 100%; height: 100%;
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
border-radius: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-mart-category-label {
|
||||||
|
z-index: 2;
|
||||||
|
position: relative;
|
||||||
|
position: -webkit-sticky;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-mart-category-label span {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 5px 6px;
|
||||||
|
background-color: #fff;
|
||||||
|
background-color: rgba(255, 255, 255, .95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-mart-emoji {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-mart-no-results {
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 70px;
|
||||||
|
color: #858585;
|
||||||
|
}
|
||||||
|
.emoji-mart-no-results span {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-mart-preview {
|
||||||
|
position: relative;
|
||||||
|
height: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-mart-preview-emoji,
|
||||||
|
.emoji-mart-preview-data,
|
||||||
|
.emoji-mart-preview-skins {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-mart-preview-emoji {
|
||||||
|
left: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-mart-preview-data {
|
||||||
|
left: 68px; right: 12px;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-mart-preview-skins {
|
||||||
|
right: 30px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-mart-preview-name {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-mart-preview-shortname {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
.emoji-mart-preview-shortname + .emoji-mart-preview-shortname,
|
||||||
|
.emoji-mart-preview-shortname + .emoji-mart-preview-emoticon,
|
||||||
|
.emoji-mart-preview-emoticon + .emoji-mart-preview-emoticon {
|
||||||
|
margin-left: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-mart-preview-emoticon {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #bbb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-mart-title span {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-mart-title .emoji-mart-emoji {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-mart-title-label {
|
||||||
|
color: #999A9C;
|
||||||
|
font-size: 26px;
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-mart-skin-swatches {
|
||||||
|
font-size: 0;
|
||||||
|
padding: 2px 0;
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
border-radius: 12px;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-mart-skin-swatches-opened .emoji-mart-skin-swatch {
|
||||||
|
width: 16px;
|
||||||
|
padding: 0 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-mart-skin-swatches-opened .emoji-mart-skin-swatch-selected:after {
|
||||||
|
opacity: .75;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-mart-skin-swatch {
|
||||||
|
display: inline-block;
|
||||||
|
width: 0;
|
||||||
|
vertical-align: middle;
|
||||||
|
transition-property: width, padding;
|
||||||
|
transition-duration: .125s;
|
||||||
|
transition-timing-function: ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-mart-skin-swatch:nth-child(1) { transition-delay: 0 }
|
||||||
|
.emoji-mart-skin-swatch:nth-child(2) { transition-delay: .03s }
|
||||||
|
.emoji-mart-skin-swatch:nth-child(3) { transition-delay: .06s }
|
||||||
|
.emoji-mart-skin-swatch:nth-child(4) { transition-delay: .09s }
|
||||||
|
.emoji-mart-skin-swatch:nth-child(5) { transition-delay: .12s }
|
||||||
|
.emoji-mart-skin-swatch:nth-child(6) { transition-delay: .15s }
|
||||||
|
|
||||||
|
.emoji-mart-skin-swatch-selected {
|
||||||
|
position: relative;
|
||||||
|
width: 16px;
|
||||||
|
padding: 0 2px;
|
||||||
|
}
|
||||||
|
.emoji-mart-skin-swatch-selected:after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 50%; left: 50%;
|
||||||
|
width: 4px; height: 4px;
|
||||||
|
margin: -2px 0 0 -2px;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 100%;
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity .2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-mart-skin {
|
||||||
|
display: inline-block;
|
||||||
|
width: 100%; padding-top: 100%;
|
||||||
|
max-width: 12px;
|
||||||
|
border-radius: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-mart-skin-tone-1 { background-color: #ffc93a }
|
||||||
|
.emoji-mart-skin-tone-2 { background-color: #fadcbc }
|
||||||
|
.emoji-mart-skin-tone-3 { background-color: #e0bb95 }
|
||||||
|
.emoji-mart-skin-tone-4 { background-color: #bf8f68 }
|
||||||
|
.emoji-mart-skin-tone-5 { background-color: #9b643d }
|
||||||
|
.emoji-mart-skin-tone-6 { background-color: #594539 }
|
|
@ -1,328 +0,0 @@
|
||||||
.collaborator-video {
|
|
||||||
z-index: 1;
|
|
||||||
position: absolute;
|
|
||||||
width: 150px;
|
|
||||||
height: 150px;
|
|
||||||
cursor: default;
|
|
||||||
color: #FFF;
|
|
||||||
}
|
|
||||||
.collaborator-video .video-receive {
|
|
||||||
position: absolute;
|
|
||||||
width: 160px;
|
|
||||||
padding: 20px 20px 20px 170px;
|
|
||||||
background: #424242;
|
|
||||||
height: 110px;
|
|
||||||
border-top-left-radius: 75px;
|
|
||||||
border-bottom-left-radius: 75px;
|
|
||||||
border-top-right-radius: 2px;
|
|
||||||
border-bottom-right-radius: 2px;
|
|
||||||
}
|
|
||||||
.collaborator-video .video-receive .video-statement {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
.collaborator-video .video-receive .btn-group .btn-yes {
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
.collaborator-video .video-receive .btn-group .btn-no {
|
|
||||||
background-color: #c04f4f;
|
|
||||||
}
|
|
||||||
.collaborator-video .video-receive .btn-group .btn-no:hover {
|
|
||||||
background-color: #A54242;
|
|
||||||
}
|
|
||||||
.collaborator-video .video-cutoff {
|
|
||||||
width: 150px;
|
|
||||||
height: 150px;
|
|
||||||
overflow: hidden;
|
|
||||||
border-radius: 75px;
|
|
||||||
z-index: 0;
|
|
||||||
position: relative;
|
|
||||||
-webkit-box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23), 10px 10px 10px rgba(0, 0, 0, 0.19);
|
|
||||||
-moz-box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23), 10px 10px 10px rgba(0, 0, 0, 0.19);
|
|
||||||
box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23), 10px 10px 10px rgba(0, 0, 0, 0.19);
|
|
||||||
}
|
|
||||||
.collaborator-video .video-cutoff video {
|
|
||||||
height: 150px;
|
|
||||||
margin-left: -25px;
|
|
||||||
}
|
|
||||||
.collaborator-video .video-cutoff .collaborator-video-avatar {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-khtml-user-select: none;
|
|
||||||
-moz-user-select: none;
|
|
||||||
-o-user-select: none;
|
|
||||||
user-select: none;
|
|
||||||
-webkit-user-drag: none;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.collaborator-video .video-audio {
|
|
||||||
position: absolute;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
top: 85%;
|
|
||||||
right: 0px;
|
|
||||||
cursor: pointer;
|
|
||||||
background: url(<%= asset_path 'audio_sprite.png' %>) no-repeat;
|
|
||||||
}
|
|
||||||
.collaborator-video .video-audio:hover {
|
|
||||||
background-position-x: -24px;
|
|
||||||
}
|
|
||||||
.collaborator-video .video-audio.active {
|
|
||||||
background-position-y: -24px;
|
|
||||||
}
|
|
||||||
.collaborator-video .video-video {
|
|
||||||
position: absolute;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
top: 85%;
|
|
||||||
left: 0px;
|
|
||||||
cursor: pointer;
|
|
||||||
background: url(<%= asset_path 'camera_sprite.png' %>) no-repeat;
|
|
||||||
}
|
|
||||||
.collaborator-video .video-video:hover {
|
|
||||||
background-position-x: -24px;
|
|
||||||
}
|
|
||||||
.collaborator-video .video-video.active {
|
|
||||||
background-position-y: -24px;
|
|
||||||
}
|
|
||||||
.collaborator-video.my-video {
|
|
||||||
left: 30px;
|
|
||||||
top: 72px;
|
|
||||||
}
|
|
||||||
#chat-box-wrapper {
|
|
||||||
height: 100%;
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
.chat-box {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
z-index: 1;
|
|
||||||
width: 300px;
|
|
||||||
height: 100%;
|
|
||||||
background: #424242;
|
|
||||||
box-shadow: -8px 0px 16px 2px rgba(0, 0, 0, 0.23);
|
|
||||||
}
|
|
||||||
.chat-box .chat-button {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: -36px;
|
|
||||||
width: 36px;
|
|
||||||
height: 49px;
|
|
||||||
background: url(<%= asset_path 'junto.png' %>) no-repeat 2px 9px, url(<%= asset_path 'tray_tab.png' %>) no-repeat;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.chat-box .chat-button.active {
|
|
||||||
background: url(<%= asset_path 'junto_spinner_dark.gif' %>) no-repeat 2px 8px, url(<%= asset_path 'tray_tab.png' %>) no-repeat !important;
|
|
||||||
}
|
|
||||||
.chat-box .chat-button .chat-unread {
|
|
||||||
background: #DAB539;
|
|
||||||
position: absolute;
|
|
||||||
top: -3px;
|
|
||||||
left: -11px;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
border-radius: 11px;
|
|
||||||
border: 2px solid #424242;
|
|
||||||
color: #424242;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: bold;
|
|
||||||
line-height: 20px;
|
|
||||||
}
|
|
||||||
.chat-box .junto-header {
|
|
||||||
width: 276px;
|
|
||||||
padding: 16px 8px 16px 16px;
|
|
||||||
font-size: 16px;
|
|
||||||
text-align: left;
|
|
||||||
font-weight: bold;
|
|
||||||
background-color: #000000;
|
|
||||||
color: #f5f5f5;
|
|
||||||
box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23);
|
|
||||||
}
|
|
||||||
.chat-box .junto-header .cursor-toggle {
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
margin-right: 8px;
|
|
||||||
margin-top: -8px;
|
|
||||||
float: right;
|
|
||||||
background: url(<%= asset_path 'cursor_sprite.png' %>) no-repeat;
|
|
||||||
}
|
|
||||||
.chat-box .junto-header .cursor-toggle:hover {
|
|
||||||
background-position-x: -32px;
|
|
||||||
}
|
|
||||||
.chat-box .junto-header .cursor-toggle.active {
|
|
||||||
background-position-y: -32px;
|
|
||||||
}
|
|
||||||
.chat-box .junto-header .video-toggle {
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
margin-right: 10px;
|
|
||||||
margin-top: -8px;
|
|
||||||
float: right;
|
|
||||||
background: url(<%= asset_path 'video_sprite.png' %>) no-repeat;
|
|
||||||
}
|
|
||||||
.chat-box .junto-header .video-toggle:hover {
|
|
||||||
background-position-x: -32px;
|
|
||||||
}
|
|
||||||
.chat-box .junto-header .video-toggle.active {
|
|
||||||
background-position-y: -32px;
|
|
||||||
}
|
|
||||||
.chat-box .participants {
|
|
||||||
width: 100%;
|
|
||||||
min-height: 150px;
|
|
||||||
padding: 16px 0px 16px 0px;
|
|
||||||
text-align: left;
|
|
||||||
color: #f5f5f5;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
.chat-box .participants .conversation-live {
|
|
||||||
padding: 5px 10px 5px 10px;
|
|
||||||
background: #c04f4f;
|
|
||||||
margin: 5px 10px;
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
.chat-box .participants .conversation-live .call-action {
|
|
||||||
float: right;
|
|
||||||
cursor: pointer;
|
|
||||||
color: #EBFF00;
|
|
||||||
}
|
|
||||||
.chat-box .participants .participant {
|
|
||||||
width: 89%;
|
|
||||||
padding: 8px 8px 2px 8px;
|
|
||||||
color: #f5f5f5;
|
|
||||||
font-family: arial, sans-serif;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 14px;
|
|
||||||
}
|
|
||||||
.chat-box .participants .participant .chat-participant-image {
|
|
||||||
width: 15%;
|
|
||||||
float: left;
|
|
||||||
overflow: hidden;
|
|
||||||
color: #BBB;
|
|
||||||
padding-top: 2px;
|
|
||||||
}
|
|
||||||
.chat-box .participants .participant .chat-participant-image img {
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
border-radius: 18px;
|
|
||||||
}
|
|
||||||
.chat-box .participants .participant .chat-participant-name {
|
|
||||||
width: 53%;
|
|
||||||
float: left;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-top: 12px;
|
|
||||||
padding: 2px 8px 0;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
.chat-box .participants .participant .chat-participant-invite-call,
|
|
||||||
.chat-box .participants .participant .chat-participant-invite-join
|
|
||||||
{
|
|
||||||
float: right;
|
|
||||||
background: #4FC059 url(<%= asset_path 'invitepeer16.png' %>) no-repeat center center;
|
|
||||||
}
|
|
||||||
.chat-box .participants .participant .chat-participant-invite-call.pending,
|
|
||||||
.chat-box .participants .participant .chat-participant-invite-join.pending {
|
|
||||||
background: #dab539 url(<%= asset_path 'ellipsis.gif' %>) no-repeat center center;
|
|
||||||
}
|
|
||||||
.chat-box .participants .participant .chat-participant-participating {
|
|
||||||
float: right;
|
|
||||||
margin-top: 14px;
|
|
||||||
}
|
|
||||||
.chat-box .participants .participant .chat-participant-participating .green-dot {
|
|
||||||
background: #4fc059;
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
.chat-box .chat-header {
|
|
||||||
width: 276px;
|
|
||||||
padding: 16px 8px 16px 16px;
|
|
||||||
font-size: 16px;
|
|
||||||
text-align: left;
|
|
||||||
font-weight: bold;
|
|
||||||
background-color: #000000;
|
|
||||||
color: #f5f5f5;
|
|
||||||
box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23);
|
|
||||||
}
|
|
||||||
.chat-box .chat-header .sound-toggle {
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
margin-right: 10px;
|
|
||||||
margin-top: -2px;
|
|
||||||
float: right;
|
|
||||||
background: url(<%= asset_path 'sound_sprite.png' %>) no-repeat;
|
|
||||||
}
|
|
||||||
.chat-box .chat-header .sound-toggle:hover {
|
|
||||||
background-position-x: -24px;
|
|
||||||
}
|
|
||||||
.chat-box .chat-header .sound-toggle.active {
|
|
||||||
background-position-y: -24px;
|
|
||||||
}
|
|
||||||
.chat-box .chat-input {
|
|
||||||
min-height: 80px;
|
|
||||||
width: 94%;
|
|
||||||
padding: 8px 3% 8px 3%;
|
|
||||||
font-size: 13px;
|
|
||||||
outline: none;
|
|
||||||
resize: none;
|
|
||||||
}
|
|
||||||
.chat-box .chat-messages {
|
|
||||||
width: 100%;
|
|
||||||
padding: 16px 0px;
|
|
||||||
overflow-y: auto;
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
.chat-box .chat-messages .chat-message {
|
|
||||||
width: 89%;
|
|
||||||
padding: 8px 8px 2px 8px;
|
|
||||||
color: #f5f5f5;
|
|
||||||
font-family: arial, sans-serif;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 14px;
|
|
||||||
}
|
|
||||||
.chat-box .chat-messages .chat-message a:link {
|
|
||||||
color: #4fb5c0;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
.chat-box .chat-messages .chat-message a:visited {
|
|
||||||
color: #aea9fd;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
.chat-box .chat-messages .chat-message a:hover {
|
|
||||||
color: #dab539;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
.chat-box .chat-messages .chat-message .chat-message-user {
|
|
||||||
width: 12%;
|
|
||||||
float: left;
|
|
||||||
overflow: hidden;
|
|
||||||
color: #BBB;
|
|
||||||
padding-top: 2px;
|
|
||||||
}
|
|
||||||
.chat-box .chat-messages .chat-message .chat-message-user img {
|
|
||||||
border: 2px solid #424242;
|
|
||||||
width: 26px;
|
|
||||||
height: 26px;
|
|
||||||
border-radius: 14px;
|
|
||||||
}
|
|
||||||
.chat-box .chat-messages .chat-message .chat-message-meta {
|
|
||||||
padding: 0 8px;
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
.chat-box .chat-messages .chat-message .chat-message-username {
|
|
||||||
color: #4fc059;
|
|
||||||
}
|
|
||||||
.chat-box .chat-messages .chat-message .chat-message-text {
|
|
||||||
width: 80%;
|
|
||||||
float: left;
|
|
||||||
padding: 2px 8px 0;
|
|
||||||
text-align: left;
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
.chat-box .chat-messages .chat-message .chat-message-time {
|
|
||||||
font-size: 10px;
|
|
||||||
color: #757575;
|
|
||||||
}
|
|
375
app/assets/stylesheets/junto.scss.erb
Normal file
375
app/assets/stylesheets/junto.scss.erb
Normal file
|
@ -0,0 +1,375 @@
|
||||||
|
.collaborator-video {
|
||||||
|
z-index: 1;
|
||||||
|
position: absolute;
|
||||||
|
width: 150px;
|
||||||
|
height: 150px;
|
||||||
|
cursor: default;
|
||||||
|
color: #FFF;
|
||||||
|
|
||||||
|
.video-receive {
|
||||||
|
position: absolute;
|
||||||
|
width: 160px;
|
||||||
|
padding: 20px 20px 20px 170px;
|
||||||
|
background: #424242;
|
||||||
|
height: 110px;
|
||||||
|
border-top-left-radius: 75px;
|
||||||
|
border-bottom-left-radius: 75px;
|
||||||
|
border-top-right-radius: 2px;
|
||||||
|
border-bottom-right-radius: 2px;
|
||||||
|
|
||||||
|
.video-statement {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.btn-group {
|
||||||
|
.btn-yes {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
.btn-no {
|
||||||
|
background-color: #c04f4f;
|
||||||
|
&:hover {
|
||||||
|
background-color: #A54242;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-cutoff {
|
||||||
|
width: 150px;
|
||||||
|
height: 150px;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 75px;
|
||||||
|
z-index: 0;
|
||||||
|
position: relative;
|
||||||
|
-webkit-box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23), 10px 10px 10px rgba(0, 0, 0, 0.19);
|
||||||
|
-moz-box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23), 10px 10px 10px rgba(0, 0, 0, 0.19);
|
||||||
|
box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23), 10px 10px 10px rgba(0, 0, 0, 0.19);
|
||||||
|
|
||||||
|
video {
|
||||||
|
height: 150px;
|
||||||
|
margin-left: -25px;
|
||||||
|
}
|
||||||
|
.collaborator-video-avatar {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-khtml-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-o-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-drag: none;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.video-audio {
|
||||||
|
position: absolute;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
top: 85%;
|
||||||
|
right: 0px;
|
||||||
|
cursor: pointer;
|
||||||
|
background: url(<%= asset_path 'audio_sprite.png' %>) no-repeat;
|
||||||
|
}
|
||||||
|
.video-audio:hover {
|
||||||
|
background-position-x: -24px;
|
||||||
|
}
|
||||||
|
.video-audio.active {
|
||||||
|
background-position-y: -24px;
|
||||||
|
}
|
||||||
|
.video-video {
|
||||||
|
position: absolute;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
top: 85%;
|
||||||
|
left: 0px;
|
||||||
|
cursor: pointer;
|
||||||
|
background: url(<%= asset_path 'camera_sprite.png' %>) no-repeat;
|
||||||
|
}
|
||||||
|
.video-video:hover {
|
||||||
|
background-position-x: -24px;
|
||||||
|
}
|
||||||
|
.video-video.active {
|
||||||
|
background-position-y: -24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.collaborator-video.my-video {
|
||||||
|
left: 30px;
|
||||||
|
top: 72px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#chat-box-wrapper {
|
||||||
|
height: 100%;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-box {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
z-index: 1;
|
||||||
|
width: 300px;
|
||||||
|
height: 100%;
|
||||||
|
background: #424242;
|
||||||
|
box-shadow: -8px 0px 16px 2px rgba(0, 0, 0, 0.23);
|
||||||
|
|
||||||
|
.chat-button {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: -36px;
|
||||||
|
width: 36px;
|
||||||
|
height: 49px;
|
||||||
|
background: url(<%= asset_path 'junto.png' %>) no-repeat 2px 9px, url(<%= asset_path 'tray_tab.png' %>) no-repeat;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: url(<%= asset_path 'junto_spinner_dark.gif' %>) no-repeat 2px 8px, url(<%= asset_path 'tray_tab.png' %>) no-repeat !important;
|
||||||
|
}
|
||||||
|
.chat-unread {
|
||||||
|
background: #DAB539;
|
||||||
|
position: absolute;
|
||||||
|
top: -3px;
|
||||||
|
left: -11px;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border-radius: 11px;
|
||||||
|
border: 2px solid #424242;
|
||||||
|
color: #424242;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.junto-header {
|
||||||
|
width: 276px;
|
||||||
|
padding: 16px 8px 16px 16px;
|
||||||
|
font-size: 16px;
|
||||||
|
text-align: left;
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: #000000;
|
||||||
|
color: #f5f5f5;
|
||||||
|
box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23);
|
||||||
|
|
||||||
|
.cursor-toggle {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
margin-right: 8px;
|
||||||
|
margin-top: -8px;
|
||||||
|
float: right;
|
||||||
|
background: url(<%= asset_path 'cursor_sprite.png' %>) no-repeat;
|
||||||
|
}
|
||||||
|
.cursor-toggle:hover {
|
||||||
|
background-position-x: -32px;
|
||||||
|
}
|
||||||
|
.cursor-toggle.active {
|
||||||
|
background-position-y: -32px;
|
||||||
|
}
|
||||||
|
.video-toggle {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
margin-right: 10px;
|
||||||
|
margin-top: -8px;
|
||||||
|
float: right;
|
||||||
|
background: url(<%= asset_path 'video_sprite.png' %>) no-repeat;
|
||||||
|
}
|
||||||
|
.video-toggle:hover {
|
||||||
|
background-position-x: -32px;
|
||||||
|
}
|
||||||
|
.video-toggle.active {
|
||||||
|
background-position-y: -32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.participants {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 150px;
|
||||||
|
padding: 16px 0px 16px 0px;
|
||||||
|
text-align: left;
|
||||||
|
color: #f5f5f5;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
.conversation-live {
|
||||||
|
padding: 5px 10px 5px 10px;
|
||||||
|
background: #c04f4f;
|
||||||
|
margin: 5px 10px;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
.conversation-live .call-action {
|
||||||
|
float: right;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #EBFF00;
|
||||||
|
}
|
||||||
|
.participant {
|
||||||
|
width: 89%;
|
||||||
|
padding: 8px 8px 2px 8px;
|
||||||
|
color: #f5f5f5;
|
||||||
|
font-family: arial, sans-serif;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 14px;
|
||||||
|
|
||||||
|
.chat-participant-image {
|
||||||
|
width: 15%;
|
||||||
|
float: left;
|
||||||
|
overflow: hidden;
|
||||||
|
color: #BBB;
|
||||||
|
padding-top: 2px;
|
||||||
|
}
|
||||||
|
.chat-participant-image img {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 18px;
|
||||||
|
}
|
||||||
|
.chat-participant-name {
|
||||||
|
width: 53%;
|
||||||
|
float: left;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-top: 12px;
|
||||||
|
padding: 2px 8px 0;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.chat-participant-invite-call,
|
||||||
|
.chat-participant-invite-join
|
||||||
|
{
|
||||||
|
float: right;
|
||||||
|
background: #4FC059 url(<%= asset_path 'invitepeer16.png' %>) no-repeat center center;
|
||||||
|
}
|
||||||
|
.chat-participant-invite-call.pending,
|
||||||
|
.chat-participant-invite-join.pending {
|
||||||
|
background: #dab539 url(<%= asset_path 'ellipsis.gif' %>) no-repeat center center;
|
||||||
|
}
|
||||||
|
.chat-participant-participating {
|
||||||
|
float: right;
|
||||||
|
margin-top: 14px;
|
||||||
|
}
|
||||||
|
.chat-participant-participating .green-dot {
|
||||||
|
background: #4fc059;
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-header {
|
||||||
|
width: 276px;
|
||||||
|
padding: 16px 8px 16px 16px;
|
||||||
|
font-size: 16px;
|
||||||
|
text-align: left;
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: #000000;
|
||||||
|
color: #f5f5f5;
|
||||||
|
box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23);
|
||||||
|
|
||||||
|
.sound-toggle {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
margin-right: 10px;
|
||||||
|
margin-top: -2px;
|
||||||
|
float: right;
|
||||||
|
background: url(<%= asset_path 'sound_sprite.png' %>) no-repeat;
|
||||||
|
}
|
||||||
|
.sound-toggle:hover {
|
||||||
|
background-position-x: -24px;
|
||||||
|
}
|
||||||
|
.sound-toggle.active {
|
||||||
|
background-position-y: -24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$chat_font_size: 16px;
|
||||||
|
|
||||||
|
.chat-input {
|
||||||
|
min-height: 80px;
|
||||||
|
width: 88%;
|
||||||
|
padding: 8px 9% 8px 3%;
|
||||||
|
font-size: $chat_font_size;
|
||||||
|
outline: none;
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-messages {
|
||||||
|
width: 100%;
|
||||||
|
padding: 16px 0px;
|
||||||
|
overflow-y: auto;
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
.chat-message {
|
||||||
|
width: 89%;
|
||||||
|
padding: 8px 8px 2px 8px;
|
||||||
|
color: #f5f5f5;
|
||||||
|
font-family: arial, sans-serif;
|
||||||
|
font-size: $chat_font_size;
|
||||||
|
line-height: $chat_font_size + 1px;
|
||||||
|
|
||||||
|
a:link {
|
||||||
|
color: #4fb5c0;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
a:visited {
|
||||||
|
color: #aea9fd;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
color: #dab539;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
.chat-message-user {
|
||||||
|
width: 12%;
|
||||||
|
float: left;
|
||||||
|
overflow: hidden;
|
||||||
|
color: #BBB;
|
||||||
|
padding-top: 2px;
|
||||||
|
}
|
||||||
|
.chat-message-user img {
|
||||||
|
border: 2px solid #424242;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 16px;
|
||||||
|
}
|
||||||
|
.chat-message-meta {
|
||||||
|
padding: 0 8px;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
.chat-message-username {
|
||||||
|
color: #4fc059;
|
||||||
|
}
|
||||||
|
.chat-message-text {
|
||||||
|
width: 80%;
|
||||||
|
float: left;
|
||||||
|
padding: 2px 8px 0;
|
||||||
|
text-align: left;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
.chat-message-time {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #757575;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-message-area {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.emoji-mart {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 98px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.extra-message-options {
|
||||||
|
height: 20px;
|
||||||
|
position: absolute;
|
||||||
|
right: 2px;
|
||||||
|
bottom: 74px;
|
||||||
|
|
||||||
|
.emoji-picker-button {
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -211,6 +211,16 @@
|
||||||
|
|
||||||
span.creatorName {
|
span.creatorName {
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
|
max-width: 162px;
|
||||||
|
display: inline-block;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.creatorAndPerm.cardHasViewOnly span.creatorName {
|
||||||
|
max-width: 95px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cardViewOnly {
|
.cardViewOnly {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
module ApplicationCable
|
module ApplicationCable
|
||||||
class Channel < ActionCable::Channel::Base
|
class Channel < ActionCable::Channel::Base
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,8 +1,17 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
class MapChannel < ApplicationCable::Channel
|
class MapChannel < ApplicationCable::Channel
|
||||||
# Called when the consumer has successfully
|
# Called when the consumer has successfully
|
||||||
# become a subscriber of this channel.
|
# become a subscriber of this channel.
|
||||||
def subscribed
|
def subscribed
|
||||||
return unless Pundit.policy(current_user, Map.find(params[:id])).show?
|
map = Map.find(params[:id])
|
||||||
|
return unless Pundit.policy(current_user, map).show?
|
||||||
stream_from "map_#{params[:id]}"
|
stream_from "map_#{params[:id]}"
|
||||||
|
Events::UserPresentOnMap.publish!(map, current_user)
|
||||||
|
end
|
||||||
|
|
||||||
|
def unsubscribed
|
||||||
|
map = Map.find(params[:id])
|
||||||
|
return unless Pundit.policy(current_user, map).show?
|
||||||
|
Events::UserNotPresentOnMap.publish!(map, current_user)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
class AccessController < ApplicationController
|
class AccessController < ApplicationController
|
||||||
before_action :require_user, only: [:access, :access_request, :approve_access, :approve_access_post,
|
before_action :require_user, only: [:access, :access_request,
|
||||||
|
:approve_access, :approve_access_post,
|
||||||
:deny_access, :deny_access_post, :request_access]
|
:deny_access, :deny_access_post, :request_access]
|
||||||
before_action :set_map, only: [:access, :access_request, :approve_access, :approve_access_post,
|
before_action :set_map, only: [:access, :access_request,
|
||||||
|
:approve_access, :approve_access_post,
|
||||||
:deny_access, :deny_access_post, :request_access]
|
:deny_access, :deny_access_post, :request_access]
|
||||||
after_action :verify_authorized
|
after_action :verify_authorized
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ module Api
|
||||||
include Pundit
|
include Pundit
|
||||||
include PunditExtra
|
include PunditExtra
|
||||||
|
|
||||||
|
protect_from_forgery with: :exception
|
||||||
snorlax_used_rest!
|
snorlax_used_rest!
|
||||||
|
|
||||||
before_action :load_resource, only: [:show, :update, :destroy]
|
before_action :load_resource, only: [:show, :update, :destroy]
|
||||||
|
@ -86,7 +87,7 @@ module Api
|
||||||
|
|
||||||
def token_user
|
def token_user
|
||||||
token = params[:access_token]
|
token = params[:access_token]
|
||||||
access_token = Token.find_by_token(token)
|
access_token = Token.find_by(token: token)
|
||||||
@token_user ||= access_token.user if access_token
|
@token_user ||= access_token.user if access_token
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -149,19 +150,30 @@ module Api
|
||||||
|
|
||||||
# override this method to explicitly set searchable columns
|
# override this method to explicitly set searchable columns
|
||||||
def searchable_columns
|
def searchable_columns
|
||||||
|
return @searchable_columns unless @searchable_columns.nil?
|
||||||
|
|
||||||
columns = resource_class.columns.select do |column|
|
columns = resource_class.columns.select do |column|
|
||||||
column.type == :text || column.type == :string
|
column.type == :text || column.type == :string
|
||||||
end
|
end
|
||||||
columns.map(&:name)
|
@searchable_columns = columns.map(&:name)
|
||||||
|
end
|
||||||
|
|
||||||
|
# e.g. ?q=test&searchfields=name,desc
|
||||||
|
def searchfields
|
||||||
|
return searchable_columns if params[:searchfields].blank?
|
||||||
|
|
||||||
|
searchfields = params[:searchfields].split(',')
|
||||||
|
searchfields.select! { |f| searchable_columns.include?(f.to_sym) }
|
||||||
|
searchfields.empty? ? searchable_columns : searchfields
|
||||||
end
|
end
|
||||||
|
|
||||||
# thanks to http://stackoverflow.com/questions/4430578
|
# thanks to http://stackoverflow.com/questions/4430578
|
||||||
def search_by_q(collection)
|
def search_by_q(collection)
|
||||||
table = resource_class.arel_table
|
table = resource_class.arel_table
|
||||||
safe_query = "%#{params[:q].gsub(/[%_]/, '\\\\\0')}%"
|
safe_query = "%#{params[:q].gsub(/[%_]/, '\\\\\0')}%"
|
||||||
search_column = -> (column) { table[column].matches(safe_query) }
|
search_column = ->(column) { table[column].matches(safe_query) }
|
||||||
|
|
||||||
condition = searchable_columns.reduce(nil) do |prev, column|
|
condition = searchfields.reduce(nil) do |prev, column|
|
||||||
next search_column.call(column) if prev.nil?
|
next search_column.call(column) if prev.nil?
|
||||||
search_column.call(column).or(prev)
|
search_column.call(column).or(prev)
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,7 +5,6 @@ class HacksController < ApplicationController
|
||||||
include ActionView::Helpers::TextHelper # string truncate method
|
include ActionView::Helpers::TextHelper # string truncate method
|
||||||
|
|
||||||
# rate limited by rack-attack - currently 5r/s
|
# rate limited by rack-attack - currently 5r/s
|
||||||
# TODO: what else can we do to make get_with_redirects safer?
|
|
||||||
def load_url_title
|
def load_url_title
|
||||||
authorize :Hack
|
authorize :Hack
|
||||||
url = params[:url]
|
url = params[:url]
|
||||||
|
|
|
@ -20,6 +20,7 @@ class MapsController < ApplicationController
|
||||||
end
|
end
|
||||||
format.json { render json: @map }
|
format.json { render json: @map }
|
||||||
format.csv { redirect_to action: :export, format: :csv }
|
format.csv { redirect_to action: :export, format: :csv }
|
||||||
|
format.ttl { redirect_to action: :export, format: :ttl }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -90,10 +91,12 @@ class MapsController < ApplicationController
|
||||||
|
|
||||||
# GET maps/:id/export
|
# GET maps/:id/export
|
||||||
def export
|
def export
|
||||||
exporter = MapExportService.new(current_user, @map)
|
exporter = MapExportService.new(current_user, @map, base_url: request.base_url)
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.json { render json: exporter.json }
|
format.json { render json: exporter.json }
|
||||||
format.csv { send_data exporter.csv }
|
format.csv { send_data exporter.csv }
|
||||||
|
format.ttl { render text: exporter.rdf }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -103,9 +106,6 @@ class MapsController < ApplicationController
|
||||||
if params[:event] == 'conversation'
|
if params[:event] == 'conversation'
|
||||||
Events::ConversationStartedOnMap.publish!(@map, current_user)
|
Events::ConversationStartedOnMap.publish!(@map, current_user)
|
||||||
valid_event = true
|
valid_event = true
|
||||||
elsif params[:event] == 'user_presence'
|
|
||||||
Events::UserPresentOnMap.publish!(@map, current_user)
|
|
||||||
valid_event = true
|
|
||||||
end
|
end
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
|
|
|
@ -55,8 +55,13 @@ class MetacodeSetsController < ApplicationController
|
||||||
@metacodes.each do |m|
|
@metacodes.each do |m|
|
||||||
InMetacodeSet.create(metacode_id: m, metacode_set_id: @metacode_set.id)
|
InMetacodeSet.create(metacode_id: m, metacode_set_id: @metacode_set.id)
|
||||||
end
|
end
|
||||||
format.html { redirect_to metacode_sets_url, notice: 'Metacode set was successfully created.' }
|
format.html do
|
||||||
format.json { render json: @metacode_set, status: :created, location: metacode_sets_url }
|
redirect_to metacode_sets_url,
|
||||||
|
notice: 'Metacode set was successfully created.'
|
||||||
|
end
|
||||||
|
format.json do
|
||||||
|
render json: @metacode_set, status: :created, location: metacode_sets_url
|
||||||
|
end
|
||||||
else
|
else
|
||||||
format.html { render action: 'new' }
|
format.html { render action: 'new' }
|
||||||
format.json { render json: @metacode_set.errors, status: :unprocessable_entity }
|
format.json { render json: @metacode_set.errors, status: :unprocessable_entity }
|
||||||
|
@ -73,20 +78,20 @@ class MetacodeSetsController < ApplicationController
|
||||||
if @metacode_set.update_attributes(metacode_set_params)
|
if @metacode_set.update_attributes(metacode_set_params)
|
||||||
|
|
||||||
# build an array of the IDs of the metacodes currently in the set
|
# build an array of the IDs of the metacodes currently in the set
|
||||||
@currentMetacodes = @metacode_set.metacodes.map { |m| m.id.to_s }
|
current_metacodes = @metacode_set.metacodes.map { |m| m.id.to_s }
|
||||||
# get the list of desired metacodes for the set from the user input and build an array out of it
|
# get the list of desired metacodes for the set from the user input and build an array out of it
|
||||||
@newMetacodes = params[:metacodes][:value].split(',')
|
new_metacodes = params[:metacodes][:value].split(',')
|
||||||
|
|
||||||
# remove the metacodes that were in it, but now aren't
|
# remove the metacodes that were in it, but now aren't
|
||||||
@removedMetacodes = @currentMetacodes - @newMetacodes
|
removed_metacodes = current_metacodes - new_metacodes
|
||||||
@removedMetacodes.each do |m|
|
removed_metacodes.each do |m|
|
||||||
@inmetacodeset = InMetacodeSet.find_by_metacode_id_and_metacode_set_id(m, @metacode_set.id)
|
inmetacodeset = InMetacodeSet.find_by(metacode_id: m, metacode_set_id: @metacode_set.id)
|
||||||
@inmetacodeset.destroy
|
inmetacodeset.destroy
|
||||||
end
|
end
|
||||||
|
|
||||||
# add the new metacodes
|
# add the new metacodes
|
||||||
@addedMetacodes = @newMetacodes - @currentMetacodes
|
added_metacodes = new_metacodes - current_metacodes
|
||||||
@addedMetacodes.each do |m|
|
added_metacodes.each do |m|
|
||||||
InMetacodeSet.create(metacode_id: m, metacode_set_id: @metacode_set.id)
|
InMetacodeSet.create(metacode_id: m, metacode_set_id: @metacode_set.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,8 @@ class SearchController < ApplicationController
|
||||||
term = params[:term]
|
term = params[:term]
|
||||||
user = params[:user] ? params[:user] : false
|
user = params[:user] ? params[:user] : false
|
||||||
|
|
||||||
if term && !term.empty? && term.downcase[0..3] != 'map:' && term.downcase[0..6] != 'mapper:' && !term.casecmp('topic:').zero?
|
if term.present? && term.downcase[0..3] != 'map:' &&
|
||||||
|
term.downcase[0..6] != 'mapper:' && !term.casecmp('topic:').zero?
|
||||||
|
|
||||||
# remove "topic:" if appended at beginning
|
# remove "topic:" if appended at beginning
|
||||||
term = term[6..-1] if term.downcase[0..5] == 'topic:'
|
term = term[6..-1] if term.downcase[0..5] == 'topic:'
|
||||||
|
@ -34,28 +35,28 @@ class SearchController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
# check whether there's a filter by metacode as part of the query
|
# check whether there's a filter by metacode as part of the query
|
||||||
filterByMetacode = false
|
filter_by_metacode = false
|
||||||
Metacode.all.each do |m|
|
Metacode.all.each do |m|
|
||||||
lOne = m.name.length + 1
|
length_one = m.name.length + 1
|
||||||
lTwo = m.name.length
|
length_two = m.name.length
|
||||||
|
|
||||||
if term.downcase[0..lTwo] == m.name.downcase + ':'
|
if term.downcase[0..length_two] == m.name.downcase + ':'
|
||||||
term = term[lOne..-1]
|
term = term[length_one..-1]
|
||||||
filterByMetacode = m
|
filter_by_metacode = m
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
search = '%' + term.downcase.strip + '%'
|
search = '%' + term.downcase.strip + '%'
|
||||||
builder = policy_scope(Topic)
|
builder = policy_scope(Topic)
|
||||||
|
|
||||||
if filterByMetacode
|
if filter_by_metacode
|
||||||
if term == ''
|
if term == ''
|
||||||
builder = builder.none
|
builder = builder.none
|
||||||
else
|
else
|
||||||
builder = builder.where('LOWER("name") like ? OR
|
builder = builder.where('LOWER("name") like ? OR
|
||||||
LOWER("desc") like ? OR
|
LOWER("desc") like ? OR
|
||||||
LOWER("link") like ?', search, search, search)
|
LOWER("link") like ?', search, search, search)
|
||||||
builder = builder.where(metacode_id: filterByMetacode.id)
|
builder = builder.where(metacode_id: filter_by_metacode.id)
|
||||||
end
|
end
|
||||||
elsif desc
|
elsif desc
|
||||||
builder = builder.where('LOWER("desc") like ?', search)
|
builder = builder.where('LOWER("desc") like ?', search)
|
||||||
|
@ -82,7 +83,8 @@ class SearchController < ApplicationController
|
||||||
term = params[:term]
|
term = params[:term]
|
||||||
user = params[:user] ? params[:user] : nil
|
user = params[:user] ? params[:user] : nil
|
||||||
|
|
||||||
if term && !term.empty? && term.downcase[0..5] != 'topic:' && term.downcase[0..6] != 'mapper:' && !term.casecmp('map:').zero?
|
if term.present? && term.downcase[0..5] != 'topic:' &&
|
||||||
|
term.downcase[0..6] != 'mapper:' && !term.casecmp('map:').zero?
|
||||||
|
|
||||||
# remove "map:" if appended at beginning
|
# remove "map:" if appended at beginning
|
||||||
term = term[4..-1] if term.downcase[0..3] == 'map:'
|
term = term[4..-1] if term.downcase[0..3] == 'map:'
|
||||||
|
@ -115,7 +117,8 @@ class SearchController < ApplicationController
|
||||||
# get /search/mappers?term=SOMETERM
|
# get /search/mappers?term=SOMETERM
|
||||||
def mappers
|
def mappers
|
||||||
term = params[:term]
|
term = params[:term]
|
||||||
if term && !term.empty? && term.downcase[0..3] != 'map:' && term.downcase[0..5] != 'topic:' && !term.casecmp('mapper:').zero?
|
if term.present? && term.downcase[0..3] != 'map:' &&
|
||||||
|
term.downcase[0..5] != 'topic:' && !term.casecmp('mapper:').zero?
|
||||||
|
|
||||||
# remove "mapper:" if appended at beginning
|
# remove "mapper:" if appended at beginning
|
||||||
term = term[7..-1] if term.downcase[0..6] == 'mapper:'
|
term = term[7..-1] if term.downcase[0..6] == 'mapper:'
|
||||||
|
@ -138,13 +141,15 @@ class SearchController < ApplicationController
|
||||||
topic2id = params[:topic2id]
|
topic2id = params[:topic2id]
|
||||||
|
|
||||||
if term && !term.empty?
|
if term && !term.empty?
|
||||||
@synapses = policy_scope(Synapse).where('LOWER("desc") like ?', '%' + term.downcase.strip + '%').order('"desc"')
|
@synapses = policy_scope(Synapse)
|
||||||
|
.where('LOWER("desc") like ?', '%' + term.downcase.strip + '%')
|
||||||
|
.order('"desc"')
|
||||||
|
|
||||||
@synapses = @synapses.uniq(&:desc)
|
@synapses = @synapses.uniq(&:desc)
|
||||||
elsif topic1id && !topic1id.empty?
|
elsif topic1id && !topic1id.empty?
|
||||||
@one = policy_scope(Synapse).where(topic1_id: topic1id, topic2_id: topic2id)
|
one = policy_scope(Synapse).where(topic1_id: topic1id, topic2_id: topic2id)
|
||||||
@two = policy_scope(Synapse).where(topic2_id: topic1id, topic1_id: topic2id)
|
two = policy_scope(Synapse).where(topic2_id: topic1id, topic1_id: topic2id)
|
||||||
@synapses = @one + @two
|
@synapses = one + two
|
||||||
@synapses.sort! { |s1, s2| s1.desc <=> s2.desc }.to_a
|
@synapses.sort! { |s1, s2| s1.desc <=> s2.desc }.to_a
|
||||||
else
|
else
|
||||||
skip_policy_scope
|
skip_policy_scope
|
||||||
|
|
|
@ -71,6 +71,8 @@ class SynapsesController < ApplicationController
|
||||||
private
|
private
|
||||||
|
|
||||||
def synapse_params
|
def synapse_params
|
||||||
params.require(:synapse).permit(:id, :desc, :category, :weight, :permission, :topic1_id, :topic2_id, :user_id)
|
params.require(:synapse).permit(
|
||||||
|
:id, :desc, :category, :weight, :permission, :topic1_id, :topic2_id, :user_id
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -11,16 +11,20 @@ class TopicsController < ApplicationController
|
||||||
def autocomplete_topic
|
def autocomplete_topic
|
||||||
term = params[:term]
|
term = params[:term]
|
||||||
if term && !term.empty?
|
if term && !term.empty?
|
||||||
@topics = policy_scope(Topic).where('LOWER("name") like ?', term.downcase + '%').order('"name"')
|
topics = policy_scope(Topic)
|
||||||
@mapTopics = @topics.select { |t| t&.metacode&.name == 'Metamap' }
|
.where('LOWER("name") like ?', term.downcase + '%')
|
||||||
|
.order('"name"')
|
||||||
|
map_topics = topics.select { |t| t&.metacode&.name == 'Metamap' }
|
||||||
# prioritize topics which point to maps, over maps
|
# prioritize topics which point to maps, over maps
|
||||||
@exclude = @mapTopics.length.positive? ? @mapTopics.map(&:name) : ['']
|
exclude = map_topics.length.positive? ? map_topics.map(&:name) : ['']
|
||||||
@maps = policy_scope(Map).where('LOWER("name") like ? AND name NOT IN (?)', term.downcase + '%', @exclude).order('"name"')
|
maps = policy_scope(Map)
|
||||||
|
.where('LOWER("name") like ? AND name NOT IN (?)', term.downcase + '%', exclude)
|
||||||
|
.order('"name"')
|
||||||
else
|
else
|
||||||
@topics = []
|
topics = []
|
||||||
@maps = []
|
maps = []
|
||||||
end
|
end
|
||||||
@all = @topics.to_a.concat(@maps.to_a).sort_by(&:name)
|
@all = topics.to_a.concat(maps.to_a).sort_by(&:name)
|
||||||
|
|
||||||
render json: autocomplete_array_json(@all).to_json
|
render json: autocomplete_array_json(@all).to_json
|
||||||
end
|
end
|
||||||
|
@ -70,13 +74,13 @@ class TopicsController < ApplicationController
|
||||||
@topic = Topic.find(params[:id])
|
@topic = Topic.find(params[:id])
|
||||||
authorize @topic
|
authorize @topic
|
||||||
|
|
||||||
topicsAlreadyHas = params[:network] ? params[:network].split(',').map(&:to_i) : []
|
topics_already_has = params[:network] ? params[:network].split(',').map(&:to_i) : []
|
||||||
|
|
||||||
alltopics = policy_scope(Topic.relatives(@topic.id, current_user)).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?
|
if params[:metacode].present?
|
||||||
alltopics.delete_if do |topic|
|
alltopics.delete_if { |topic| topic.metacode_id != params[:metacode].to_i }
|
||||||
!topicsAlreadyHas.index(topic.id).nil?
|
|
||||||
end
|
end
|
||||||
|
alltopics.delete_if { |topic| !topics_already_has.index(topic.id).nil? }
|
||||||
|
|
||||||
@json = Hash.new(0)
|
@json = Hash.new(0)
|
||||||
alltopics.each do |t|
|
alltopics.each do |t|
|
||||||
|
@ -93,12 +97,14 @@ class TopicsController < ApplicationController
|
||||||
@topic = Topic.find(params[:id])
|
@topic = Topic.find(params[:id])
|
||||||
authorize @topic
|
authorize @topic
|
||||||
|
|
||||||
topicsAlreadyHas = params[:network] ? params[:network].split(',').map(&:to_i) : []
|
topics_already_has = params[:network] ? params[:network].split(',').map(&:to_i) : []
|
||||||
|
|
||||||
alltopics = policy_scope(Topic.relatives(@topic.id, current_user)).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?
|
if params[:metacode].present?
|
||||||
|
alltopics.delete_if { |topic| topic.metacode_id != params[:metacode].to_i }
|
||||||
|
end
|
||||||
alltopics.delete_if do |topic|
|
alltopics.delete_if do |topic|
|
||||||
!topicsAlreadyHas.index(topic.id.to_s).nil?
|
!topics_already_has.index(topic.id.to_s).nil?
|
||||||
end
|
end
|
||||||
|
|
||||||
# find synapses between topics in alltopics array
|
# find synapses between topics in alltopics array
|
||||||
|
@ -108,9 +114,9 @@ class TopicsController < ApplicationController
|
||||||
!synapse_ids.index(synapse.id).nil?
|
!synapse_ids.index(synapse.id).nil?
|
||||||
end
|
end
|
||||||
|
|
||||||
creatorsAlreadyHas = params[:creators] ? params[:creators].split(',').map(&:to_i) : []
|
creators_already_has = params[:creators] ? params[:creators].split(',').map(&:to_i) : []
|
||||||
allcreators = (alltopics.map(&:user) + allsynapses.map(&:user)).uniq.delete_if do |user|
|
allcreators = (alltopics.map(&:user) + allsynapses.map(&:user)).uniq.delete_if do |user|
|
||||||
!creatorsAlreadyHas.index(user.id).nil?
|
!creators_already_has.index(user.id).nil?
|
||||||
end
|
end
|
||||||
|
|
||||||
@json = {}
|
@json = {}
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
class Users::PasswordsController < Devise::PasswordsController
|
module Users
|
||||||
protected
|
class PasswordsController < Devise::PasswordsController
|
||||||
|
protected
|
||||||
|
|
||||||
def after_resetting_password_path_for(resource)
|
def after_resetting_password_path_for(resource)
|
||||||
signed_in_root_path(resource)
|
signed_in_root_path(resource)
|
||||||
end
|
end
|
||||||
|
|
||||||
def after_sending_reset_password_instructions_path_for(_resource_name)
|
def after_sending_reset_password_instructions_path_for(_resource_name)
|
||||||
sign_in_path if is_navigational_format?
|
sign_in_path if is_navigational_format?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,37 +1,39 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
class Users::RegistrationsController < Devise::RegistrationsController
|
module Users
|
||||||
before_action :configure_sign_up_params, only: [:create]
|
class RegistrationsController < Devise::RegistrationsController
|
||||||
before_action :configure_account_update_params, only: [:update]
|
before_action :configure_sign_up_params, only: [:create]
|
||||||
after_action :store_location, only: [:new]
|
before_action :configure_account_update_params, only: [:update]
|
||||||
|
after_action :store_location, only: [:new]
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
def after_update_path_for(resource)
|
def after_update_path_for(resource)
|
||||||
signed_in_root_path(resource)
|
signed_in_root_path(resource)
|
||||||
end
|
end
|
||||||
|
|
||||||
def after_sign_in_path_for(resource)
|
def after_sign_in_path_for(resource)
|
||||||
stored = stored_location_for(User)
|
stored = stored_location_for(User)
|
||||||
return stored if stored
|
return stored if stored
|
||||||
|
|
||||||
if request.referer&.match(sign_in_url) || request.referer&.match(sign_up_url)
|
if request.referer&.match(sign_in_url) || request.referer&.match(sign_up_url)
|
||||||
super
|
super
|
||||||
else
|
else
|
||||||
request.referer || root_path
|
request.referer || root_path
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def store_location
|
||||||
|
store_location_for(User, params[:redirect_to]) if params[:redirect_to]
|
||||||
|
end
|
||||||
|
|
||||||
|
def configure_sign_up_params
|
||||||
|
devise_parameter_sanitizer.permit(:sign_up, keys: [:name, :joinedwithcode])
|
||||||
|
end
|
||||||
|
|
||||||
|
def configure_account_update_params
|
||||||
|
devise_parameter_sanitizer.permit(:account_update, keys: [:image])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def store_location
|
|
||||||
store_location_for(User, params[:redirect_to]) if params[:redirect_to]
|
|
||||||
end
|
|
||||||
|
|
||||||
def configure_sign_up_params
|
|
||||||
devise_parameter_sanitizer.permit(:sign_up, keys: [:name, :joinedwithcode])
|
|
||||||
end
|
|
||||||
|
|
||||||
def configure_account_update_params
|
|
||||||
devise_parameter_sanitizer.permit(:account_update, keys: [:image])
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,12 +2,11 @@
|
||||||
module ApplicationHelper
|
module ApplicationHelper
|
||||||
def metacodeset
|
def metacodeset
|
||||||
metacodes = current_user.settings.metacodes
|
metacodes = current_user.settings.metacodes
|
||||||
|
|
||||||
return false unless metacodes[0].include?('metacodeset')
|
return false unless metacodes[0].include?('metacodeset')
|
||||||
if metacodes[0].sub('metacodeset-', '') == 'Most'
|
return 'Most' if metacodes[0].sub('metacodeset-', '') == 'Most'
|
||||||
return 'Most'
|
return 'Recent' if metacodes[0].sub('metacodeset-', '') == 'Recent'
|
||||||
elsif metacodes[0].sub('metacodeset-', '') == 'Recent'
|
|
||||||
return 'Recent'
|
|
||||||
end
|
|
||||||
MetacodeSet.find(metacodes[0].sub('metacodeset-', '').to_i)
|
MetacodeSet.find(metacodes[0].sub('metacodeset-', '').to_i)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ module TopicsHelper
|
||||||
def autocomplete_array_json(topics)
|
def autocomplete_array_json(topics)
|
||||||
topics.map do |t|
|
topics.map do |t|
|
||||||
is_map = t.is_a?(Map)
|
is_map = t.is_a?(Map)
|
||||||
metamapMetacode = Metacode.find_by_name('Metamap')
|
metamap_metacode = Metacode.find_by(name: 'Metamap')
|
||||||
{
|
{
|
||||||
id: t.id,
|
id: t.id,
|
||||||
label: t.name,
|
label: t.name,
|
||||||
|
@ -17,8 +17,8 @@ module TopicsHelper
|
||||||
rtype: is_map ? 'map' : 'topic',
|
rtype: is_map ? 'map' : 'topic',
|
||||||
inmaps: is_map ? [] : t.inmaps(current_user),
|
inmaps: is_map ? [] : t.inmaps(current_user),
|
||||||
inmapsLinks: is_map ? [] : t.inmapsLinks(current_user),
|
inmapsLinks: is_map ? [] : t.inmapsLinks(current_user),
|
||||||
type: is_map ? metamapMetacode.name : t.metacode.name,
|
type: is_map ? metamap_metacode.name : t.metacode.name,
|
||||||
typeImageURL: is_map ? metamapMetacode.icon : t.metacode.icon,
|
typeImageURL: is_map ? metamap_metacode.icon : t.metacode.icon,
|
||||||
mapCount: is_map ? 0 : t.maps.count,
|
mapCount: is_map ? 0 : t.maps.count,
|
||||||
synapseCount: is_map ? 0 : t.synapses.count
|
synapseCount: is_map ? 0 : t.synapses.count
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ class AccessRequest < ApplicationRecord
|
||||||
Mailboxer::Receipt.where(notification: notification).update_all(is_read: true)
|
Mailboxer::Receipt.where(notification: notification).update_all(is_read: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
user_map = UserMap.create(user: user, map: map)
|
UserMap.create(user: user, map: map)
|
||||||
NotificationService.access_approved(self)
|
NotificationService.access_approved(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
38
app/models/attachment.rb
Normal file
38
app/models/attachment.rb
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
class Attachment < ApplicationRecord
|
||||||
|
belongs_to :attachable, polymorphic: true
|
||||||
|
|
||||||
|
has_attached_file :file,
|
||||||
|
styles: lambda { |a|
|
||||||
|
if a.instance.image?
|
||||||
|
{
|
||||||
|
thumb: 'x128#',
|
||||||
|
medium: 'x320>'
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{}
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
validates_attachment_content_type :file, content_type: Attachable.allowed_types
|
||||||
|
|
||||||
|
def image?
|
||||||
|
Attachable.image_types.include?(file.instance.file_content_type)
|
||||||
|
end
|
||||||
|
|
||||||
|
def audio?
|
||||||
|
Attachable.audio_types.include?(file.instance.file_content_type)
|
||||||
|
end
|
||||||
|
|
||||||
|
def text?
|
||||||
|
Attachable.text_types.include?(file.instance.file_content_type)
|
||||||
|
end
|
||||||
|
|
||||||
|
def pdf?
|
||||||
|
Attachable.pdf_types.include?(file.instance.file_content_type)
|
||||||
|
end
|
||||||
|
|
||||||
|
def document?
|
||||||
|
text? || pdf?
|
||||||
|
end
|
||||||
|
end
|
50
app/models/concerns/attachable.rb
Normal file
50
app/models/concerns/attachable.rb
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
module Attachable
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
has_many :attachments, as: :attachable, dependent: :destroy
|
||||||
|
end
|
||||||
|
|
||||||
|
def images
|
||||||
|
attachments.where(file_content_type: image_types)
|
||||||
|
end
|
||||||
|
|
||||||
|
def audios
|
||||||
|
attachments.where(file_content_type: audio_types)
|
||||||
|
end
|
||||||
|
|
||||||
|
def texts
|
||||||
|
attachments.where(file_content_type: text_types)
|
||||||
|
end
|
||||||
|
|
||||||
|
def pdfs
|
||||||
|
attachments.where(file_content_type: pdf_types)
|
||||||
|
end
|
||||||
|
|
||||||
|
def documents
|
||||||
|
attachments.where(file_content_type: text_types + pdf_types)
|
||||||
|
end
|
||||||
|
|
||||||
|
class << self
|
||||||
|
def image_types
|
||||||
|
['image/png', 'image/gif', 'image/jpeg']
|
||||||
|
end
|
||||||
|
|
||||||
|
def audio_types
|
||||||
|
['audio/ogg', 'audio/mp3']
|
||||||
|
end
|
||||||
|
|
||||||
|
def text_types
|
||||||
|
['text/plain']
|
||||||
|
end
|
||||||
|
|
||||||
|
def pdf_types
|
||||||
|
['application/pdf']
|
||||||
|
end
|
||||||
|
|
||||||
|
def allowed_types
|
||||||
|
image_types + audio_types + text_types + pdf_types
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,9 +1,10 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
class Event < ApplicationRecord
|
class Event < ApplicationRecord
|
||||||
KINDS = %w(user_present_on_map conversation_started_on_map
|
KINDS = %w(user_present_on_map user_not_present_on_map
|
||||||
topic_added_to_map topic_moved_on_map topic_removed_from_map
|
conversation_started_on_map
|
||||||
synapse_added_to_map synapse_removed_from_map
|
topic_added_to_map topic_moved_on_map topic_removed_from_map
|
||||||
topic_updated synapse_updated).freeze
|
synapse_added_to_map synapse_removed_from_map
|
||||||
|
topic_updated synapse_updated).freeze
|
||||||
|
|
||||||
belongs_to :eventable, polymorphic: true
|
belongs_to :eventable, polymorphic: true
|
||||||
belongs_to :map
|
belongs_to :map
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
class Events::ConversationStartedOnMap < Event
|
module Events
|
||||||
# after_create :notify_users!
|
class ConversationStartedOnMap < Event
|
||||||
|
# after_create :notify_users!
|
||||||
|
|
||||||
def self.publish!(map, user)
|
def self.publish!(map, user)
|
||||||
create!(kind: 'conversation_started_on_map',
|
create!(kind: 'conversation_started_on_map',
|
||||||
eventable: map,
|
eventable: map,
|
||||||
map: map,
|
map: map,
|
||||||
user: user)
|
user: user)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
class Events::SynapseAddedToMap < Event
|
module Events
|
||||||
# after_create :notify_users!
|
class SynapseAddedToMap < Event
|
||||||
|
# after_create :notify_users!
|
||||||
|
|
||||||
def self.publish!(synapse, map, user, meta)
|
def self.publish!(synapse, map, user, meta)
|
||||||
create!(kind: 'synapse_added_to_map',
|
create!(kind: 'synapse_added_to_map',
|
||||||
eventable: synapse,
|
eventable: synapse,
|
||||||
map: map,
|
map: map,
|
||||||
user: user,
|
user: user,
|
||||||
meta: meta)
|
meta: meta)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
class Events::SynapseRemovedFromMap < Event
|
module Events
|
||||||
# after_create :notify_users!
|
class SynapseRemovedFromMap < Event
|
||||||
|
# after_create :notify_users!
|
||||||
|
|
||||||
def self.publish!(synapse, map, user, meta)
|
def self.publish!(synapse, map, user, meta)
|
||||||
create!(kind: 'synapse_removed_from_map',
|
create!(kind: 'synapse_removed_from_map',
|
||||||
eventable: synapse,
|
eventable: synapse,
|
||||||
map: map,
|
map: map,
|
||||||
user: user,
|
user: user,
|
||||||
meta: meta)
|
meta: meta)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
class Events::SynapseUpdated < Event
|
module Events
|
||||||
# after_create :notify_users!
|
class SynapseUpdated < Event
|
||||||
|
# after_create :notify_users!
|
||||||
|
|
||||||
def self.publish!(synapse, user, meta)
|
def self.publish!(synapse, user, meta)
|
||||||
create!(kind: 'synapse_updated',
|
create!(kind: 'synapse_updated',
|
||||||
eventable: synapse,
|
eventable: synapse,
|
||||||
user: user,
|
user: user,
|
||||||
meta: meta)
|
meta: meta)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
class Events::TopicAddedToMap < Event
|
module Events
|
||||||
# after_create :notify_users!
|
class TopicAddedToMap < Event
|
||||||
|
# after_create :notify_users!
|
||||||
|
|
||||||
def self.publish!(topic, map, user, meta)
|
def self.publish!(topic, map, user, meta)
|
||||||
create!(kind: 'topic_added_to_map',
|
create!(kind: 'topic_added_to_map',
|
||||||
eventable: topic,
|
eventable: topic,
|
||||||
map: map,
|
map: map,
|
||||||
user: user,
|
user: user,
|
||||||
meta: meta)
|
meta: meta)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
class Events::TopicMovedOnMap < Event
|
module Events
|
||||||
# after_create :notify_users!
|
class TopicMovedOnMap < Event
|
||||||
|
# after_create :notify_users!
|
||||||
|
|
||||||
def self.publish!(topic, map, user, meta)
|
def self.publish!(topic, map, user, meta)
|
||||||
create!(kind: 'topic_moved_on_map',
|
create!(kind: 'topic_moved_on_map',
|
||||||
eventable: topic,
|
eventable: topic,
|
||||||
map: map,
|
map: map,
|
||||||
user: user,
|
user: user,
|
||||||
meta: meta)
|
meta: meta)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
class Events::TopicRemovedFromMap < Event
|
module Events
|
||||||
# after_create :notify_users!
|
class TopicRemovedFromMap < Event
|
||||||
|
# after_create :notify_users!
|
||||||
|
|
||||||
def self.publish!(topic, map, user, meta)
|
def self.publish!(topic, map, user, meta)
|
||||||
create!(kind: 'topic_removed_from_map',
|
create!(kind: 'topic_removed_from_map',
|
||||||
eventable: topic,
|
eventable: topic,
|
||||||
map: map,
|
map: map,
|
||||||
user: user,
|
user: user,
|
||||||
meta: meta)
|
meta: meta)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
class Events::TopicUpdated < Event
|
module Events
|
||||||
# after_create :notify_users!
|
class TopicUpdated < Event
|
||||||
|
# after_create :notify_users!
|
||||||
|
|
||||||
def self.publish!(topic, user, meta)
|
def self.publish!(topic, user, meta)
|
||||||
create!(kind: 'topic_updated',
|
create!(kind: 'topic_updated',
|
||||||
eventable: topic,
|
eventable: topic,
|
||||||
user: user,
|
user: user,
|
||||||
meta: meta)
|
meta: meta)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
13
app/models/events/user_not_present_on_map.rB
Normal file
13
app/models/events/user_not_present_on_map.rB
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
module Events
|
||||||
|
class UserNotPresentOnMap < Event
|
||||||
|
# after_create :notify_users!
|
||||||
|
|
||||||
|
def self.publish!(map, user)
|
||||||
|
create!(kind: 'user_not_present_on_map',
|
||||||
|
eventable: map,
|
||||||
|
map: map,
|
||||||
|
user: user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,11 +1,13 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
class Events::UserPresentOnMap < Event
|
module Events
|
||||||
# after_create :notify_users!
|
class UserPresentOnMap < Event
|
||||||
|
# after_create :notify_users!
|
||||||
|
|
||||||
def self.publish!(map, user)
|
def self.publish!(map, user)
|
||||||
create!(kind: 'user_present_on_map',
|
create!(kind: 'user_present_on_map',
|
||||||
eventable: map,
|
eventable: map,
|
||||||
map: map,
|
map: map,
|
||||||
user: user)
|
user: user)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,8 +3,10 @@ class Map < ApplicationRecord
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
belongs_to :source, class_name: :Map
|
belongs_to :source, class_name: :Map
|
||||||
|
|
||||||
has_many :topicmappings, -> { Mapping.topicmapping }, class_name: :Mapping, dependent: :destroy
|
has_many :topicmappings, -> { Mapping.topicmapping },
|
||||||
has_many :synapsemappings, -> { Mapping.synapsemapping }, class_name: :Mapping, dependent: :destroy
|
class_name: :Mapping, dependent: :destroy
|
||||||
|
has_many :synapsemappings, -> { Mapping.synapsemapping },
|
||||||
|
class_name: :Mapping, dependent: :destroy
|
||||||
has_many :topics, through: :topicmappings, source: :mappable, source_type: 'Topic'
|
has_many :topics, through: :topicmappings, source: :mappable, source_type: 'Topic'
|
||||||
has_many :synapses, through: :synapsemappings, source: :mappable, source_type: 'Synapse'
|
has_many :synapses, through: :synapsemappings, source: :mappable, source_type: 'Synapse'
|
||||||
has_many :messages, as: :resource, dependent: :destroy
|
has_many :messages, as: :resource, dependent: :destroy
|
||||||
|
@ -21,7 +23,6 @@ class Map < ApplicationRecord
|
||||||
has_attached_file :screenshot,
|
has_attached_file :screenshot,
|
||||||
styles: {
|
styles: {
|
||||||
thumb: ['220x220#', :png]
|
thumb: ['220x220#', :png]
|
||||||
#:full => ['940x630#', :png]
|
|
||||||
},
|
},
|
||||||
default_url: 'https://s3.amazonaws.com/metamaps-assets/site/missing-map-square.png'
|
default_url: 'https://s3.amazonaws.com/metamaps-assets/site/missing-map-square.png'
|
||||||
|
|
||||||
|
@ -31,7 +32,7 @@ class Map < ApplicationRecord
|
||||||
validates :permission, inclusion: { in: Perm::ISSIONS.map(&:to_s) }
|
validates :permission, inclusion: { in: Perm::ISSIONS.map(&:to_s) }
|
||||||
|
|
||||||
# Validate the attached image is image/jpg, image/png, etc
|
# Validate the attached image is image/jpg, image/png, etc
|
||||||
validates_attachment_content_type :screenshot, content_type: /\Aimage\/.*\Z/
|
validates_attachment_content_type :screenshot, content_type: %r{\Aimage/.*\Z}
|
||||||
|
|
||||||
after_update :after_updated
|
after_update :after_updated
|
||||||
after_save :update_deferring_topics_and_synapses, if: :permission_changed?
|
after_save :update_deferring_topics_and_synapses, if: :permission_changed?
|
||||||
|
@ -80,7 +81,12 @@ class Map < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def as_json(_options = {})
|
def as_json(_options = {})
|
||||||
json = super(methods: [:user_name, :user_image, :star_count, :topic_count, :synapse_count, :contributor_count, :collaborator_ids, :screenshot_url], except: [:screenshot_content_type, :screenshot_file_size, :screenshot_file_name, :screenshot_updated_at])
|
json = super(
|
||||||
|
methods: [:user_name, :user_image, :star_count, :topic_count, :synapse_count,
|
||||||
|
:contributor_count, :collaborator_ids, :screenshot_url],
|
||||||
|
except: [:screenshot_content_type, :screenshot_file_size, :screenshot_file_name,
|
||||||
|
:screenshot_updated_at]
|
||||||
|
)
|
||||||
json[:created_at_clean] = created_at_str
|
json[:created_at_clean] = created_at_str
|
||||||
json[:updated_at_clean] = updated_at_str
|
json[:updated_at_clean] = updated_at_str
|
||||||
json
|
json
|
||||||
|
@ -120,17 +126,16 @@ class Map < ApplicationRecord
|
||||||
end
|
end
|
||||||
removed.compact
|
removed.compact
|
||||||
end
|
end
|
||||||
|
|
||||||
def after_updated
|
def after_updated
|
||||||
attrs = ['name', 'desc', 'permission']
|
attrs = %w(name desc permission)
|
||||||
if attrs.any? {|k| changed_attributes.key?(k)}
|
return unless attrs.any? { |k| changed_attributes.key?(k) }
|
||||||
ActionCable.server.broadcast 'map_' + id.to_s, type: 'mapUpdated'
|
ActionCable.server.broadcast 'map_' + id.to_s, type: 'mapUpdated'
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_deferring_topics_and_synapses
|
def update_deferring_topics_and_synapses
|
||||||
Topic.where(defer_to_map_id: id).update_all(permission: permission)
|
Topic.where(defer_to_map_id: id).update(permission: permission)
|
||||||
Synapse.where(defer_to_map_id: id).update_all(permission: permission)
|
Synapse.where(defer_to_map_id: id).update(permission: permission)
|
||||||
end
|
end
|
||||||
|
|
||||||
def invited_text
|
def invited_text
|
||||||
|
|
|
@ -27,7 +27,7 @@ class Mapping < ApplicationRecord
|
||||||
|
|
||||||
def after_created
|
def after_created
|
||||||
if mappable_type == 'Topic'
|
if mappable_type == 'Topic'
|
||||||
meta = {'mapping_id': id}
|
meta = { 'x': xloc, 'y': yloc, 'mapping_id': id }
|
||||||
Events::TopicAddedToMap.publish!(mappable, map, user, meta)
|
Events::TopicAddedToMap.publish!(mappable, map, user, meta)
|
||||||
ActionCable.server.broadcast 'map_' + map.id.to_s, type: 'topicAdded', topic: mappable.filtered, mapping_id: id
|
ActionCable.server.broadcast 'map_' + map.id.to_s, type: 'topicAdded', topic: mappable.filtered, mapping_id: id
|
||||||
elsif mappable_type == 'Synapse'
|
elsif mappable_type == 'Synapse'
|
||||||
|
@ -38,13 +38,14 @@ class Mapping < ApplicationRecord
|
||||||
synapse: mappable.filtered,
|
synapse: mappable.filtered,
|
||||||
topic1: mappable.topic1.filtered,
|
topic1: mappable.topic1.filtered,
|
||||||
topic2: mappable.topic2.filtered,
|
topic2: mappable.topic2.filtered,
|
||||||
mapping_id: id)
|
mapping_id: id
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def after_updated
|
def after_updated
|
||||||
if mappable_type == 'Topic' and (xloc_changed? or yloc_changed?)
|
if (mappable_type == 'Topic') && (xloc_changed? || yloc_changed?)
|
||||||
meta = {'x': xloc, 'y': yloc, 'mapping_id': id}
|
meta = { 'x': xloc, 'y': yloc, 'mapping_id': id }
|
||||||
Events::TopicMovedOnMap.publish!(mappable, map, updated_by, meta)
|
Events::TopicMovedOnMap.publish!(mappable, map, updated_by, meta)
|
||||||
ActionCable.server.broadcast 'map_' + map.id.to_s, type: 'topicMoved', id: mappable.id, mapping_id: id, x: xloc, y: yloc
|
ActionCable.server.broadcast 'map_' + map.id.to_s, type: 'topicMoved', id: mappable.id, mapping_id: id, x: xloc, y: yloc
|
||||||
end
|
end
|
||||||
|
@ -57,7 +58,7 @@ class Mapping < ApplicationRecord
|
||||||
mappable.save
|
mappable.save
|
||||||
end
|
end
|
||||||
|
|
||||||
meta = {'mapping_id': id}
|
meta = { 'mapping_id': id }
|
||||||
if mappable_type == 'Topic'
|
if mappable_type == 'Topic'
|
||||||
Events::TopicRemovedFromMap.publish!(mappable, map, updated_by, meta)
|
Events::TopicRemovedFromMap.publish!(mappable, map, updated_by, meta)
|
||||||
ActionCable.server.broadcast 'map_' + map.id.to_s, type: 'topicRemoved', id: mappable.id, mapping_id: id
|
ActionCable.server.broadcast 'map_' + map.id.to_s, type: 'topicRemoved', id: mappable.id, mapping_id: id
|
||||||
|
|
|
@ -4,7 +4,7 @@ class Message < ApplicationRecord
|
||||||
belongs_to :resource, polymorphic: true
|
belongs_to :resource, polymorphic: true
|
||||||
|
|
||||||
delegate :name, to: :user, prefix: true
|
delegate :name, to: :user, prefix: true
|
||||||
|
|
||||||
after_create :after_created
|
after_create :after_created
|
||||||
|
|
||||||
def user_image
|
def user_image
|
||||||
|
@ -15,8 +15,8 @@ class Message < ApplicationRecord
|
||||||
json = super(methods: [:user_name, :user_image])
|
json = super(methods: [:user_name, :user_image])
|
||||||
json
|
json
|
||||||
end
|
end
|
||||||
|
|
||||||
def after_created
|
def after_created
|
||||||
ActionCable.server.broadcast 'map_' + resource.id.to_s, type: 'messageCreated', message: self.as_json
|
ActionCable.server.broadcast 'map_' + resource.id.to_s, type: 'messageCreated', message: as_json
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -22,6 +22,7 @@ class Synapse < ApplicationRecord
|
||||||
where(topic1_id: topic_id).or(where(topic2_id: topic_id))
|
where(topic1_id: topic_id).or(where(topic2_id: topic_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
before_create :set_perm_by_defer
|
||||||
after_update :after_updated
|
after_update :after_updated
|
||||||
|
|
||||||
delegate :name, to: :user, prefix: true
|
delegate :name, to: :user, prefix: true
|
||||||
|
@ -51,17 +52,35 @@ class Synapse < ApplicationRecord
|
||||||
super(methods: [:user_name, :user_image, :collaborator_ids])
|
super(methods: [:user_name, :user_image, :collaborator_ids])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def as_rdf
|
||||||
|
output = ''
|
||||||
|
output += %(d:synapse_#{id} a mm:Synapse ;\n)
|
||||||
|
output += %( mm:topic1 d:topic_#{topic1_id} ;\n)
|
||||||
|
output += %( mm:topic2 d:topic_#{topic2_id} ;\n)
|
||||||
|
output += %( mm:direction "#{category}" ;\n)
|
||||||
|
output += %( rdfs:comment "#{desc}" ;\n) if desc.present?
|
||||||
|
output[-2] = '.'
|
||||||
|
output += %(\n)
|
||||||
|
output
|
||||||
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
def set_perm_by_defer
|
||||||
|
permission = defer_to_map.permission if defer_to_map
|
||||||
|
end
|
||||||
|
|
||||||
def after_updated
|
def after_updated
|
||||||
attrs = ['desc', 'category', 'permission', 'defer_to_map_id']
|
attrs = %w(desc category permission defer_to_map_id)
|
||||||
if attrs.any? {|k| changed_attributes.key?(k)}
|
if attrs.any? { |k| changed_attributes.key?(k) }
|
||||||
new = self.attributes.select {|k| attrs.include?(k) }
|
new = attributes.select { |k| attrs.include?(k) }
|
||||||
old = changed_attributes.select {|k| attrs.include?(k) }
|
old = changed_attributes.select { |k| attrs.include?(k) }
|
||||||
meta = new.merge(old) # we are prioritizing the old values, keeping them
|
meta = new.merge(old) # we are prioritizing the old values, keeping them
|
||||||
meta['changed'] = changed_attributes.keys.select {|k| attrs.include?(k) }
|
meta['changed'] = changed_attributes.keys.select { |k| attrs.include?(k) }
|
||||||
Events::SynapseUpdated.publish!(self, user, meta)
|
Events::SynapseUpdated.publish!(self, user, meta)
|
||||||
maps.each {|map|
|
maps.each do |map|
|
||||||
ActionCable.server.broadcast 'map_' + map.id.to_s, type: 'synapseUpdated', id: id
|
ActionCable.server.broadcast 'map_' + map.id.to_s, type: 'synapseUpdated', id: id
|
||||||
}
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
class Topic < ApplicationRecord
|
class Topic < ApplicationRecord
|
||||||
include TopicsHelper
|
include TopicsHelper
|
||||||
|
include Attachable
|
||||||
|
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
belongs_to :defer_to_map, class_name: 'Map', foreign_key: 'defer_to_map_id'
|
belongs_to :defer_to_map, class_name: 'Map', foreign_key: 'defer_to_map_id'
|
||||||
|
@ -15,29 +16,13 @@ class Topic < ApplicationRecord
|
||||||
|
|
||||||
belongs_to :metacode
|
belongs_to :metacode
|
||||||
|
|
||||||
|
before_create :set_perm_by_defer
|
||||||
before_create :create_metamap?
|
before_create :create_metamap?
|
||||||
after_update :after_updated
|
after_update :after_updated
|
||||||
|
|
||||||
validates :permission, presence: true
|
validates :permission, presence: true
|
||||||
validates :permission, inclusion: { in: Perm::ISSIONS.map(&:to_s) }
|
validates :permission, inclusion: { in: Perm::ISSIONS.map(&:to_s) }
|
||||||
|
|
||||||
# This method associates the attribute ":image" with a file attachment
|
|
||||||
has_attached_file :image
|
|
||||||
|
|
||||||
# , styles: {
|
|
||||||
# thumb: '100x100>',
|
|
||||||
# square: '200x200#',
|
|
||||||
# medium: '300x300>'
|
|
||||||
# }
|
|
||||||
|
|
||||||
# Validate the attached image is image/jpg, image/png, etc
|
|
||||||
validates_attachment_content_type :image, content_type: /\Aimage\/.*\Z/
|
|
||||||
|
|
||||||
# This method associates the attribute ":image" with a file attachment
|
|
||||||
has_attached_file :audio
|
|
||||||
# Validate the attached audio is audio/wav, audio/mp3, etc
|
|
||||||
validates_attachment_content_type :audio, content_type: /\Aaudio\/.*\Z/
|
|
||||||
|
|
||||||
def synapses
|
def synapses
|
||||||
synapses1.or(synapses2)
|
synapses1.or(synapses2)
|
||||||
end
|
end
|
||||||
|
@ -82,6 +67,19 @@ class Topic < ApplicationRecord
|
||||||
map_count: map_count(options[:user]), synapse_count: synapse_count(options[:user]))
|
map_count: map_count(options[:user]), synapse_count: synapse_count(options[:user]))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def as_rdf
|
||||||
|
output = ''
|
||||||
|
output += %(d:topic_#{id} a mm:Topic ;\n)
|
||||||
|
output += %( rdfs:label "#{name}" ;\n)
|
||||||
|
output += %( rdfs:comment "#{desc}" ;\n) if desc.present?
|
||||||
|
output += %( foaf:homepage <#{link}> ;\n) if link.present?
|
||||||
|
output += %( mm:mapper d:mapper_#{user_id} ;\n)
|
||||||
|
output += %( mm:metacode "#{metacode.name}" ;\n)
|
||||||
|
output[-2] = '.' # change last ; to a .
|
||||||
|
output += %(\n)
|
||||||
|
output
|
||||||
|
end
|
||||||
|
|
||||||
def collaborator_ids
|
def collaborator_ids
|
||||||
if defer_to_map
|
if defer_to_map
|
||||||
defer_to_map.editors.select { |mapper| mapper != user }.map(&:id)
|
defer_to_map.editors.select { |mapper| mapper != user }.map(&:id)
|
||||||
|
@ -137,6 +135,10 @@ class Topic < ApplicationRecord
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
|
def set_perm_by_defer
|
||||||
|
permission = defer_to_map.permission if defer_to_map
|
||||||
|
end
|
||||||
|
|
||||||
def create_metamap?
|
def create_metamap?
|
||||||
return unless (link == '') && (metacode.name == 'Metamap')
|
return unless (link == '') && (metacode.name == 'Metamap')
|
||||||
|
|
||||||
|
@ -147,16 +149,16 @@ class Topic < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def after_updated
|
def after_updated
|
||||||
attrs = ['name', 'desc', 'link', 'metacode_id', 'permission', 'defer_to_map_id']
|
attrs = %w(name desc link metacode_id permission defer_to_map_id)
|
||||||
if attrs.any? {|k| changed_attributes.key?(k)}
|
if attrs.any? { |k| changed_attributes.key?(k) }
|
||||||
new = self.attributes.select {|k| attrs.include?(k) }
|
new = attributes.select { |k| attrs.include?(k) }
|
||||||
old = changed_attributes.select {|k| attrs.include?(k) }
|
old = changed_attributes.select { |k| attrs.include?(k) }
|
||||||
meta = new.merge(old) # we are prioritizing the old values, keeping them
|
meta = new.merge(old) # we are prioritizing the old values, keeping them
|
||||||
meta['changed'] = changed_attributes.keys.select {|k| attrs.include?(k) }
|
meta['changed'] = changed_attributes.keys.select { |k| attrs.include?(k) }
|
||||||
Events::TopicUpdated.publish!(self, user, meta)
|
Events::TopicUpdated.publish!(self, user, meta)
|
||||||
maps.each {|map|
|
maps.each do |map|
|
||||||
ActionCable.server.broadcast 'map_' + map.id.to_s, type: 'topicUpdated', id: id
|
ActionCable.server.broadcast 'map_' + map.id.to_s, type: 'topicUpdated', id: id
|
||||||
}
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -89,6 +89,17 @@ class User < ApplicationRecord
|
||||||
}.to_a.sort{ |a, b| b[1] <=> a[1] }.map{|i| i[0]}.slice(0, 5)
|
}.to_a.sort{ |a, b| b[1] <=> a[1] }.map{|i| i[0]}.slice(0, 5)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def as_rdf(opts = {})
|
||||||
|
base_url = opts[:base_url] || 'https://metamaps.cc'
|
||||||
|
output = ''
|
||||||
|
output += %(d:mapper_#{id} a foaf:OnlineAccount ;\n)
|
||||||
|
output += %( foaf:accountName "#{name}" ;\n)
|
||||||
|
output += %( foaf:accountServiceHomepage "#{base_url}/mapper/#{id}" ;\n)
|
||||||
|
output[-2] = '.' # change last ; to a .
|
||||||
|
output += %(\n)
|
||||||
|
output
|
||||||
|
end
|
||||||
|
|
||||||
def all_accessible_maps
|
def all_accessible_maps
|
||||||
maps + shared_maps
|
maps + shared_maps
|
||||||
end
|
end
|
||||||
|
@ -115,7 +126,7 @@ class User < ApplicationRecord
|
||||||
if code == joinedwithcode
|
if code == joinedwithcode
|
||||||
update(generation: 0)
|
update(generation: 0)
|
||||||
else
|
else
|
||||||
update(generation: User.find_by_code(joinedwithcode).generation + 1)
|
update(generation: User.find_by(code: joinedwithcode).generation + 1)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ class UserPreference
|
||||||
array = []
|
array = []
|
||||||
%w(Action Aim Idea Question Note Wildcard Subject).each do |m|
|
%w(Action Aim Idea Question Note Wildcard Subject).each do |m|
|
||||||
begin
|
begin
|
||||||
metacode = Metacode.find_by_name(m)
|
metacode = Metacode.find_by(name: m)
|
||||||
array.push(metacode.id.to_s) if metacode
|
array.push(metacode.id.to_s) if metacode
|
||||||
rescue ActiveRecord::StatementInvalid
|
rescue ActiveRecord::StatementInvalid
|
||||||
if m == 'Action'
|
if m == 'Action'
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
class Webhooks::Slack::SynapseRemovedFromMap < Webhooks::Slack::Base
|
class Webhooks::Slack::SynapseRemovedFromMap < Webhooks::Slack::Base
|
||||||
def text
|
def text
|
||||||
connector = eventable.desc.empty? ? '->' : eventable.desc
|
connector = eventable.desc.empty? ? '->' : eventable.desc
|
||||||
# todo express correct directionality of arrows when desc is empty
|
# TODO: express correct directionality of arrows when desc is empty
|
||||||
"\"*#{eventable.topic1.name}* #{connector} *#{eventable.topic2.name}*\" was removed by *#{event.user.name}* as a connection from the map *#{view_map_on_metamaps}*"
|
"\"*#{eventable.topic1.name}* #{connector} *#{eventable.topic2.name}*\" was removed by *#{event.user.name}* as a connection from the map *#{view_map_on_metamaps}*"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -35,13 +35,29 @@ module Api
|
||||||
Pundit.policy_scope(scope[:current_user], object.send(attr))&.map(&:id) || []
|
Pundit.policy_scope(scope[:current_user], object.send(attr))&.map(&:id) || []
|
||||||
end
|
end
|
||||||
has_many(attr, opts.merge(if: -> { embeds.include?(key) })) do
|
has_many(attr, opts.merge(if: -> { embeds.include?(key) })) do
|
||||||
Pundit.policy_scope(scope[:current_user], object.send(attr)) || []
|
list = Pundit.policy_scope(scope[:current_user], object.send(attr)) || []
|
||||||
|
child_serializer = "Api::V2::#{attr.to_s.singularize.camelize}Serializer".constantize
|
||||||
|
resource = ActiveModelSerializers::SerializableResource.new(
|
||||||
|
list,
|
||||||
|
each_serializer: child_serializer,
|
||||||
|
scope: scope.merge(embeds: [])
|
||||||
|
)
|
||||||
|
resource.as_json
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
id_opts = opts.merge(key: "#{key}_id")
|
id_opts = opts.merge(key: "#{key}_id")
|
||||||
attribute("#{attr}_id".to_sym,
|
attribute("#{attr}_id".to_sym,
|
||||||
id_opts.merge(unless: -> { embeds.include?(key) }))
|
id_opts.merge(unless: -> { embeds.include?(key) }))
|
||||||
attribute(key, opts.merge(if: -> { embeds.include?(key) }))
|
attribute(key, opts.merge(if: -> { embeds.include?(key) })) do |serializer|
|
||||||
|
object = serializer.object.send(key)
|
||||||
|
child_serializer = "Api::V2::#{object.class.name}Serializer".constantize
|
||||||
|
resource = ActiveModelSerializers::SerializableResource.new(
|
||||||
|
object,
|
||||||
|
serializer: child_serializer,
|
||||||
|
scope: scope.merge(embeds: [])
|
||||||
|
)
|
||||||
|
resource.as_json
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
class MapExportService
|
class MapExportService
|
||||||
attr_reader :user, :map
|
attr_reader :user, :map, :base_url
|
||||||
def initialize(user, map)
|
|
||||||
|
def initialize(user, map, opts = {})
|
||||||
@user = user
|
@user = user
|
||||||
@map = map
|
@map = map
|
||||||
|
@base_url = opts[:base_url] || 'https://metamaps.cc'
|
||||||
end
|
end
|
||||||
|
|
||||||
def json
|
def json
|
||||||
|
@ -22,6 +24,25 @@ class MapExportService
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def rdf
|
||||||
|
output = ''
|
||||||
|
output += "PREFIX d: <#{base_url}/maps/#{map.id}>\n"
|
||||||
|
output += "PREFIX mm: <#{base_url}/owl/map.owl.ttl>\n"
|
||||||
|
output += "PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>\n"
|
||||||
|
output += "PREFIX foaf: <http://xmlns.com/foaf/0.1/>\n"
|
||||||
|
output += "\n"
|
||||||
|
map.contributors.each do |mapper|
|
||||||
|
output += mapper.as_rdf(base_url: base_url)
|
||||||
|
end
|
||||||
|
map.topics.each do |topic|
|
||||||
|
output += topic.as_rdf
|
||||||
|
end
|
||||||
|
map.synapses.each do |synapse|
|
||||||
|
output += synapse.as_rdf
|
||||||
|
end
|
||||||
|
output
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def topic_headings
|
def topic_headings
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
Metamaps.currentPage = "mapper";
|
Metamaps.currentPage = "mapper";
|
||||||
Metamaps.ServerData.Mapper = {
|
Metamaps.ServerData.Mapper = {
|
||||||
models: <%= @maps.to_json.html_safe %>,
|
models: <%= @maps.to_json.html_safe %>,
|
||||||
id: <%= params[:id] %>
|
mapperId: <%= params[:id] %>
|
||||||
};
|
};
|
||||||
Metamaps.GlobalUI.Search.focus();
|
Metamaps.GlobalUI.Search.focus();
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -15,6 +15,19 @@
|
||||||
<title><%= yield(:title) %></title>
|
<title><%= yield(:title) %></title>
|
||||||
<%= csrf_meta_tags %>
|
<%= csrf_meta_tags %>
|
||||||
<meta name="viewport" content="width=device-width, user-scalable=no">
|
<meta name="viewport" content="width=device-width, user-scalable=no">
|
||||||
|
|
||||||
|
<% if controller.class.name == 'MapsController' && @map %>
|
||||||
|
<meta property="og:title" content="<%= @map.name %>" />
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<meta property="og:image" content="<%= @map.screenshot_url %>" />
|
||||||
|
<meta property="og:description" content="<%= @map.desc %>" />
|
||||||
|
<meta property="og:url" content="<%= request.original_url %>" />
|
||||||
|
|
||||||
|
<meta name="twitter:title" content="<%= @map.name %>" />
|
||||||
|
<meta name="twitter:image" content="<%= @map.screenshot_url %>" />
|
||||||
|
<meta name="twitter:description" content="<%= @map.desc %>" />
|
||||||
|
<meta name="twitter:url" content="<%= request.original_url %>" />
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<%= stylesheet_link_tag "application", :media => "all" %>
|
<%= stylesheet_link_tag "application", :media => "all" %>
|
||||||
<%= javascript_include_tag "application" %>
|
<%= javascript_include_tag "application" %>
|
||||||
|
|
|
@ -34,6 +34,11 @@
|
||||||
<p class="mapCreatedAt"><span>Created by:</span> {{user_name}} on {{created_at}}</p>
|
<p class="mapCreatedAt"><span>Created by:</span> {{user_name}} on {{created_at}}</p>
|
||||||
<p class="mapEditedAt"><span>Last edited:</span> {{updated_at}}</p>
|
<p class="mapEditedAt"><span>Last edited:</span> {{updated_at}}</p>
|
||||||
<div class="mapInfoButtonsWrapper">
|
<div class="mapInfoButtonsWrapper">
|
||||||
|
<div class="mapInfoThumbnail">
|
||||||
|
<div class="thumbnail"></div>
|
||||||
|
<div class="tooltip">Update Thumbnail</div>
|
||||||
|
<span>Thumb</span>
|
||||||
|
</div>
|
||||||
<div class="mapInfoDelete">
|
<div class="mapInfoDelete">
|
||||||
<div class="deleteMap"></div>
|
<div class="deleteMap"></div>
|
||||||
<span>Delete</span>
|
<span>Delete</span>
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
<% if current_user %>
|
<% if current_user %>
|
||||||
<div class="requestTitle">
|
<div class="requestTitle">
|
||||||
Click here to name this map!
|
Click here to name this map
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
@ -77,6 +77,11 @@
|
||||||
<p class="mapCreatedAt"><span>Created by:</span> <%= @map.user == user ? "You" : @map.user.name %> on <%= @map.created_at.strftime("%m/%d/%Y") %></p>
|
<p class="mapCreatedAt"><span>Created by:</span> <%= @map.user == user ? "You" : @map.user.name %> on <%= @map.created_at.strftime("%m/%d/%Y") %></p>
|
||||||
<p class="mapEditedAt"><span>Last edited:</span> <%= @map.updated_at.strftime("%m/%d/%Y") %></p>
|
<p class="mapEditedAt"><span>Last edited:</span> <%= @map.updated_at.strftime("%m/%d/%Y") %></p>
|
||||||
<div class="mapInfoButtonsWrapper">
|
<div class="mapInfoButtonsWrapper">
|
||||||
|
<div class="mapInfoThumbnail">
|
||||||
|
<div class="thumbnail"></div>
|
||||||
|
<div class="tooltip">Update Thumbnail</div>
|
||||||
|
<span>Thumb</span>
|
||||||
|
</div>
|
||||||
<div class="mapInfoDelete">
|
<div class="mapInfoDelete">
|
||||||
<div class="deleteMap"></div>
|
<div class="deleteMap"></div>
|
||||||
<span>Delete</span>
|
<span>Delete</span>
|
||||||
|
|
|
@ -29,4 +29,7 @@ Rails.application.configure do
|
||||||
# Expands the lines which load the assets
|
# Expands the lines which load the assets
|
||||||
config.assets.debug = false
|
config.assets.debug = false
|
||||||
config.assets.quiet = true
|
config.assets.quiet = true
|
||||||
|
|
||||||
|
# S3 file storage
|
||||||
|
config.paperclip_defaults = {} # store on local machine for dev
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,3 +3,6 @@
|
||||||
|
|
||||||
# Add new mime types for use in respond_to blocks:
|
# Add new mime types for use in respond_to blocks:
|
||||||
# Mime::Type.register "text/richtext", :rtf
|
# Mime::Type.register "text/richtext", :rtf
|
||||||
|
|
||||||
|
# RDF export
|
||||||
|
Mime::Type.register 'text/turtle', :ttl
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
class Rack::Attack
|
|
||||||
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
|
|
||||||
|
|
||||||
# Throttle all requests by IP (60rpm)
|
|
||||||
#
|
|
||||||
# Key: "rack::attack:#{Time.now.to_i/:period}:req/ip:#{req.ip}"
|
|
||||||
# throttle('req/ip', :limit => 300, :period => 5.minutes) do |req|
|
|
||||||
# req.ip # unless req.path.start_with?('/assets')
|
|
||||||
# end
|
|
||||||
|
|
||||||
# Throttle POST requests to /login by IP address
|
|
||||||
#
|
|
||||||
# Key: "rack::attack:#{Time.now.to_i/:period}:logins/ip:#{req.ip}"
|
|
||||||
throttle('logins/ip', limit: 5, period: 20.seconds) do |req|
|
|
||||||
req.ip if req.path == '/login' && req.post?
|
|
||||||
end
|
|
||||||
|
|
||||||
# Throttle POST requests to /login by email param
|
|
||||||
#
|
|
||||||
# Key: "rack::attack:#{Time.now.to_i/:period}:logins/email:#{req.email}"
|
|
||||||
#
|
|
||||||
# Note: This creates a problem where a malicious user could intentionally
|
|
||||||
# throttle logins for another user and force their login requests to be
|
|
||||||
# denied, but that's not very common and shouldn't happen to you. (Knock
|
|
||||||
# on wood!)
|
|
||||||
throttle('logins/email', limit: 5, period: 20.seconds) do |req|
|
|
||||||
if req.path == '/login' && req.post?
|
|
||||||
# return the email if present, nil otherwise
|
|
||||||
req.params['email'].presence
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
throttle('load_url_title/req/5mins/ip', limit: 300, period: 5.minutes) do |req|
|
|
||||||
req.ip if req.path == 'hacks/load_url_title'
|
|
||||||
end
|
|
||||||
throttle('load_url_title/req/1s/ip', limit: 5, period: 1.second) do |req|
|
|
||||||
# If the return value is truthy, the cache key for the return value
|
|
||||||
# is incremented and compared with the limit. In this case:
|
|
||||||
# "rack::attack:#{Time.now.to_i/1.second}:load_url_title/req/ip:#{req.ip}"
|
|
||||||
#
|
|
||||||
# If falsy, the cache key is neither incremented nor checked.
|
|
||||||
|
|
||||||
req.ip if req.path == 'hacks/load_url_title'
|
|
||||||
end
|
|
||||||
|
|
||||||
self.throttled_response = lambda do |env|
|
|
||||||
now = Time.now
|
|
||||||
match_data = env['rack.attack.match_data']
|
|
||||||
period = match_data[:period]
|
|
||||||
limit = match_data[:limit]
|
|
||||||
|
|
||||||
headers = {
|
|
||||||
'X-RateLimit-Limit' => limit.to_s,
|
|
||||||
'X-RateLimit-Remaining' => '0',
|
|
||||||
'X-RateLimit-Reset' => (now + (period - now.to_i % period)).to_s
|
|
||||||
}
|
|
||||||
|
|
||||||
[429, headers, ['']]
|
|
||||||
end
|
|
||||||
end
|
|
63
config/initializers/rack_attack.rb
Normal file
63
config/initializers/rack_attack.rb
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
module Rack
|
||||||
|
class Attack
|
||||||
|
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
|
||||||
|
|
||||||
|
# Throttle all requests by IP (60rpm)
|
||||||
|
#
|
||||||
|
# Key: "rack::attack:#{Time.now.to_i/:period}:req/ip:#{req.ip}"
|
||||||
|
# throttle('req/ip', :limit => 300, :period => 5.minutes) do |req|
|
||||||
|
# req.ip # unless req.path.start_with?('/assets')
|
||||||
|
# end
|
||||||
|
|
||||||
|
# Throttle POST requests to /login by IP address
|
||||||
|
#
|
||||||
|
# Key: "rack::attack:#{Time.now.to_i/:period}:logins/ip:#{req.ip}"
|
||||||
|
throttle('logins/ip', limit: 5, period: 20.seconds) do |req|
|
||||||
|
req.ip if req.path == '/login' && req.post?
|
||||||
|
end
|
||||||
|
|
||||||
|
# Throttle POST requests to /login by email param
|
||||||
|
#
|
||||||
|
# Key: "rack::attack:#{Time.now.to_i/:period}:logins/email:#{req.email}"
|
||||||
|
#
|
||||||
|
# Note: This creates a problem where a malicious user could intentionally
|
||||||
|
# throttle logins for another user and force their login requests to be
|
||||||
|
# denied, but that's not very common and shouldn't happen to you. (Knock
|
||||||
|
# on wood!)
|
||||||
|
throttle('logins/email', limit: 5, period: 20.seconds) do |req|
|
||||||
|
if req.path == '/login' && req.post?
|
||||||
|
# return the email if present, nil otherwise
|
||||||
|
req.params['email'].presence
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
throttle('load_url_title/req/5mins/ip', limit: 300, period: 5.minutes) do |req|
|
||||||
|
req.ip if req.path == 'hacks/load_url_title'
|
||||||
|
end
|
||||||
|
throttle('load_url_title/req/1s/ip', limit: 5, period: 1.second) do |req|
|
||||||
|
# If the return value is truthy, the cache key for the return value
|
||||||
|
# is incremented and compared with the limit. In this case:
|
||||||
|
# "rack::attack:#{Time.now.to_i/1.second}:load_url_title/req/ip:#{req.ip}"
|
||||||
|
#
|
||||||
|
# If falsy, the cache key is neither incremented nor checked.
|
||||||
|
|
||||||
|
req.ip if req.path == 'hacks/load_url_title'
|
||||||
|
end
|
||||||
|
|
||||||
|
self.throttled_response = lambda do |env|
|
||||||
|
now = Time.zone.now
|
||||||
|
match_data = env['rack.attack.match_data']
|
||||||
|
period = match_data[:period]
|
||||||
|
limit = match_data[:limit]
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
'X-RateLimit-Limit' => limit.to_s,
|
||||||
|
'X-RateLimit-Remaining' => '0',
|
||||||
|
'X-RateLimit-Reset' => (now + (period - now.to_i % period)).to_s
|
||||||
|
}
|
||||||
|
|
||||||
|
[429, headers, ['']]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,4 +1,4 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
METAMAPS_VERSION = '3.0.1'
|
METAMAPS_VERSION = '3.2'
|
||||||
METAMAPS_BUILD = `git log -1 --pretty=%H`.chomp[0..11].freeze
|
METAMAPS_BUILD = `git log -1 --pretty=%H`.chomp[0..11].freeze
|
||||||
METAMAPS_LAST_UPDATED = `git log -1 --pretty='%ad'`.split(' ').values_at(1, 2, 4).join(' ').freeze
|
METAMAPS_LAST_UPDATED = `git log -1 --pretty='%ad'`.split(' ').values_at(1, 2, 4).join(' ').freeze
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
Warden::Manager.after_set_user do |user,auth,opts|
|
# frozen_string_literal: true
|
||||||
|
Warden::Manager.after_set_user do |user, auth, opts|
|
||||||
scope = opts[:scope]
|
scope = opts[:scope]
|
||||||
auth.cookies.signed["#{scope}.id"] = user.id
|
auth.cookies.signed["#{scope}.id"] = user.id
|
||||||
auth.cookies.signed["#{scope}.expires_at"] = 30.minutes.from_now
|
auth.cookies.signed["#{scope}.expires_at"] = 30.minutes.from_now
|
||||||
end
|
end
|
||||||
Warden::Manager.before_logout do |user, auth, opts|
|
Warden::Manager.before_logout do |_user, auth, opts|
|
||||||
scope = opts[:scope]
|
scope = opts[:scope]
|
||||||
auth.cookies.signed["#{scope}.id"] = nil
|
auth.cookies.signed["#{scope}.id"] = nil
|
||||||
auth.cookies.signed["#{scope}.expires_at"] = nil
|
auth.cookies.signed["#{scope}.expires_at"] = nil
|
||||||
|
|
|
@ -14,6 +14,7 @@ Metamaps::Application.routes.draw do
|
||||||
get 'starred'
|
get 'starred'
|
||||||
get 'mapper/:id', action: 'mapper'
|
get 'mapper/:id', action: 'mapper'
|
||||||
end
|
end
|
||||||
|
get :explore, to: redirect('/')
|
||||||
|
|
||||||
resources :maps, except: [:index, :edit] do
|
resources :maps, except: [:index, :edit] do
|
||||||
member do
|
member do
|
||||||
|
|
12
db/migrate/20170122201451_create_attachments.rb
Normal file
12
db/migrate/20170122201451_create_attachments.rb
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
class CreateAttachments < ActiveRecord::Migration[5.0]
|
||||||
|
def change
|
||||||
|
create_table :attachments do |t|
|
||||||
|
t.references :attachable, polymorphic: true
|
||||||
|
t.attachment :file
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
|
||||||
|
remove_attachment :topics, :image
|
||||||
|
remove_attachment :topics, :audio
|
||||||
|
end
|
||||||
|
end
|
26
db/schema.rb
26
db/schema.rb
|
@ -10,7 +10,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(version: 20161218183817) do
|
ActiveRecord::Schema.define(version: 20170122201451) do
|
||||||
|
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
@ -26,6 +26,18 @@ ActiveRecord::Schema.define(version: 20161218183817) do
|
||||||
t.index ["user_id"], name: "index_access_requests_on_user_id", using: :btree
|
t.index ["user_id"], name: "index_access_requests_on_user_id", using: :btree
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "attachments", force: :cascade do |t|
|
||||||
|
t.string "attachable_type"
|
||||||
|
t.integer "attachable_id"
|
||||||
|
t.string "file_file_name"
|
||||||
|
t.string "file_content_type"
|
||||||
|
t.integer "file_file_size"
|
||||||
|
t.datetime "file_updated_at"
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
t.index ["attachable_type", "attachable_id"], name: "index_attachments_on_attachable_type_and_attachable_id", using: :btree
|
||||||
|
end
|
||||||
|
|
||||||
create_table "delayed_jobs", force: :cascade do |t|
|
create_table "delayed_jobs", force: :cascade do |t|
|
||||||
t.integer "priority", default: 0, null: false
|
t.integer "priority", default: 0, null: false
|
||||||
t.integer "attempts", default: 0, null: false
|
t.integer "attempts", default: 0, null: false
|
||||||
|
@ -269,17 +281,9 @@ ActiveRecord::Schema.define(version: 20161218183817) do
|
||||||
t.text "link"
|
t.text "link"
|
||||||
t.integer "user_id"
|
t.integer "user_id"
|
||||||
t.integer "metacode_id"
|
t.integer "metacode_id"
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
t.text "permission"
|
t.text "permission"
|
||||||
t.string "image_file_name", limit: 255
|
|
||||||
t.string "image_content_type", limit: 255
|
|
||||||
t.integer "image_file_size"
|
|
||||||
t.datetime "image_updated_at"
|
|
||||||
t.string "audio_file_name", limit: 255
|
|
||||||
t.string "audio_content_type", limit: 255
|
|
||||||
t.integer "audio_file_size"
|
|
||||||
t.datetime "audio_updated_at"
|
|
||||||
t.integer "defer_to_map_id"
|
t.integer "defer_to_map_id"
|
||||||
t.index ["metacode_id"], name: "index_topics_on_metacode_id", using: :btree
|
t.index ["metacode_id"], name: "index_topics_on_metacode_id", using: :btree
|
||||||
t.index ["user_id"], name: "index_topics_on_user_id", using: :btree
|
t.index ["user_id"], name: "index_topics_on_user_id", using: :btree
|
||||||
|
|
10
db/seeds.rb
10
db/seeds.rb
|
@ -38,7 +38,7 @@ Metacode.create(name: 'Process',
|
||||||
manual_icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/blueprint/96px/bp_process.png',
|
manual_icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/blueprint/96px/bp_process.png',
|
||||||
color: '#BDB25E')
|
color: '#BDB25E')
|
||||||
|
|
||||||
Metacode.create(name: 'Future',
|
Metacode.create(name: 'Future Dev',
|
||||||
manual_icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/blueprint/96px/bp_futuredev.png',
|
manual_icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/blueprint/96px/bp_futuredev.png',
|
||||||
color: '#25A17F')
|
color: '#25A17F')
|
||||||
|
|
||||||
|
@ -70,7 +70,7 @@ Metacode.create(name: 'Need',
|
||||||
manual_icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/blueprint/96px/bp_need.png',
|
manual_icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/blueprint/96px/bp_need.png',
|
||||||
color: '#D2A7D4')
|
color: '#D2A7D4')
|
||||||
|
|
||||||
Metacode.create(name: 'Open',
|
Metacode.create(name: 'Open Issue',
|
||||||
manual_icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/blueprint/96px/bp_openissue.png',
|
manual_icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/blueprint/96px/bp_openissue.png',
|
||||||
color: '#9BBF71')
|
color: '#9BBF71')
|
||||||
|
|
||||||
|
@ -142,7 +142,7 @@ Metacode.create(name: 'Aim',
|
||||||
manual_icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/generics/96px/gen_aim.png',
|
manual_icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/generics/96px/gen_aim.png',
|
||||||
color: '#B0B0B0')
|
color: '#B0B0B0')
|
||||||
|
|
||||||
Metacode.create(name: 'Good',
|
Metacode.create(name: 'Good Practice',
|
||||||
manual_icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/generics/96px/gen_goodpractice.png',
|
manual_icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/generics/96px/gen_goodpractice.png',
|
||||||
color: '#BD9E86')
|
color: '#BD9E86')
|
||||||
|
|
||||||
|
@ -198,6 +198,10 @@ Metacode.create(name: 'Status',
|
||||||
manual_icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/generics/96px/gen_status.png',
|
manual_icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/generics/96px/gen_status.png',
|
||||||
color: '#EFA7C0')
|
color: '#EFA7C0')
|
||||||
|
|
||||||
|
Metacode.create(name: 'Story',
|
||||||
|
manual_icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/generics/96px/gen_story.png',
|
||||||
|
color: '#A7A2DC')
|
||||||
|
|
||||||
Metacode.create(name: 'Tool',
|
Metacode.create(name: 'Tool',
|
||||||
manual_icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/generics/96px/gen_tool.png',
|
manual_icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/generics/96px/gen_tool.png',
|
||||||
color: '#828282')
|
color: '#828282')
|
||||||
|
|
|
@ -4,3 +4,8 @@ queryParameters:
|
||||||
Search text columns for this string. A query of <code>"example"</code> will be passed to SQL as <code>LIKE %example%</code>. The searchable columns are: <pre><< searchFields >></pre>
|
Search text columns for this string. A query of <code>"example"</code> will be passed to SQL as <code>LIKE %example%</code>. The searchable columns are: <pre><< searchFields >></pre>
|
||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
|
searchfields:
|
||||||
|
description: |
|
||||||
|
A comma-seperated list of columns to search. For instance, to search a topic's name and description (but not link field) for the string "cognition", you could use `?q=cognition&searchfields=name,desc`.
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
|
|
@ -14,12 +14,28 @@
|
||||||
|
|
||||||
#### Setup Postgres
|
#### Setup Postgres
|
||||||
|
|
||||||
sudo apt-get install postgresql-9.4 #specify version!!
|
sudo apt-get install postgresql-9.4
|
||||||
|
# make sure you have development headers for postgres. The package name might be different on your distribution.
|
||||||
|
sudo apt-get install libpq-dev
|
||||||
sudo -u postgres psql
|
sudo -u postgres psql
|
||||||
postgres=# CREATE USER metamaps WITH PASSWORD 'mycoolpassword' CREATEDB;
|
postgres=# CREATE USER metamaps WITH PASSWORD 'mycoolpassword' CREATEDB;
|
||||||
postgres=# CREATE DATABASE metamap002_production OWNER metamaps;
|
postgres=# CREATE DATABASE metamaps_production OWNER metamaps;
|
||||||
postgres=# \q
|
postgres=# \q
|
||||||
|
|
||||||
|
On some deploys, we have had problems with unicode encoding when trying to run `db:setup`. Running the commands in this Github gist resolved the issue: https://gist.github.com/amolkhanorkar/8706915. Try this link if you have problems
|
||||||
|
|
||||||
|
#### Install Node for javascript building
|
||||||
|
|
||||||
|
# this first line lets us use up-to-date versions of node.js
|
||||||
|
# instead of the old versions in the Ubuntu repositories
|
||||||
|
curl -sL https://deb.nodesource.com/setup_7.x | sudo -E bash -
|
||||||
|
sudo apt-get install nodejs
|
||||||
|
sudo ln -s /usr/bin/nodejs /usr/bin/node
|
||||||
|
|
||||||
|
### Install redis server for action cable
|
||||||
|
|
||||||
|
sudo apt-get install redis-server
|
||||||
|
|
||||||
#### Install system-wide rvm:
|
#### Install system-wide rvm:
|
||||||
|
|
||||||
sudo gpg --keyserver hkp://keys.gnupg.net \
|
sudo gpg --keyserver hkp://keys.gnupg.net \
|
||||||
|
@ -38,8 +54,15 @@
|
||||||
rvm user gemsets
|
rvm user gemsets
|
||||||
git clone https://github.com/metamaps/metamaps \
|
git clone https://github.com/metamaps/metamaps \
|
||||||
--branch instance/mycoolinstance
|
--branch instance/mycoolinstance
|
||||||
rvm install $(cat metamaps/.ruby-version) #ensure ruby is installed
|
cat metamaps/.ruby-version
|
||||||
cd metamaps
|
|
||||||
|
The last line tells you what version of ruby you need to install. For example, at the time of writing the version is 2.3.0. As your normal sudo-enabled user, run
|
||||||
|
|
||||||
|
sudo rvm install 2.3.0
|
||||||
|
|
||||||
|
Now switch back to the metamaps user and continue
|
||||||
|
|
||||||
|
cd /home/metamaps/metamaps
|
||||||
gem install bundler
|
gem install bundler
|
||||||
RAILS_ENV=production bundle install
|
RAILS_ENV=production bundle install
|
||||||
|
|
||||||
|
@ -60,16 +83,11 @@ Run this in the metamaps directory, still as metamaps:
|
||||||
# create, load schema, seed
|
# create, load schema, seed
|
||||||
bundle exec rails db:setup
|
bundle exec rails db:setup
|
||||||
|
|
||||||
#### Install node & ES6 modules
|
|
||||||
|
|
||||||
sudo aptitude install nodejs npm
|
|
||||||
sudo ln -s /usr/bin/nodejs /usr/bin/node
|
|
||||||
npm install
|
|
||||||
|
|
||||||
#### Precompile assets
|
#### Precompile assets
|
||||||
|
|
||||||
This step depends on running npm install first; assets:precompile calls `npm install` and `bin/build-apidocs.sh`, both of which require node_modules to be installed. We suggest you run the commands separately this time to better catch any errors.
|
Note that `rails assets:precompile` will normally call `npm install` and `bin/build-apidocs.sh` as part of its process. Both of these latter commands require `npm install` to be run first. We suggest you run all five commands separately this time (like below) to better catch any errors. In the future, you won't need to run the second and third commands separately.
|
||||||
|
|
||||||
|
npm install
|
||||||
npm run build
|
npm run build
|
||||||
bin/build-apidocs.sh
|
bin/build-apidocs.sh
|
||||||
bundle exec rails assets:precompile
|
bundle exec rails assets:precompile
|
||||||
|
@ -93,17 +111,18 @@ server to see what problems show up:
|
||||||
#### Realtime server:
|
#### Realtime server:
|
||||||
|
|
||||||
sudo npm install -g forever
|
sudo npm install -g forever
|
||||||
(crontab -u metamaps -l 2>/dev/null; echo "@reboot env NODE_REALTIME_PORT=5000 $(which forever) --append -l /home/metamaps/logs/forever.realtime.log start /home/metamaps/metamaps/realtime/realtime-server.js") | crontab -u metamaps -
|
(sudo crontab -u metamaps -l 2>/dev/null; echo "@reboot NODE_REALTIME_PORT=5000 /usr/bin/forever --minUptime 1000 --spinSleepTime 1000 --append -l /home/metamaps/logs/forever.realtime.log -c /home/metamaps/metamaps/node_modules/.bin/babel-node --workingDir /home/metamaps/metamaps start /home/metamaps/metamaps/realtime/realtime-server.js") | sudo crontab -u metamaps
|
||||||
|
|
||||||
mkdir -p /home/metamaps/logs
|
mkdir -p /home/metamaps/logs
|
||||||
env NODE_REALTIME_PORT=5000 forever --append \
|
/usr/bin/forever --minUptime 1000 --spinSleepTime 1000 \
|
||||||
|
--append -l /home/metamaps/logs/forever.realtime.log \
|
||||||
-c /home/metamaps/metamaps/node_modules/.bin/babel-node \
|
-c /home/metamaps/metamaps/node_modules/.bin/babel-node \
|
||||||
-l /home/metamaps/logs/forever.realtime.log \
|
--workingDir /home/metamaps/metamaps \
|
||||||
start /home/metamaps/metamaps/realtime/realtime-server.js
|
start /home/metamaps/metamaps/realtime/realtime-server.js
|
||||||
|
|
||||||
#### Upstart service for delayed_worker:
|
#### Upstart service for delayed_worker:
|
||||||
|
|
||||||
Put the following code into `/etc/init/metamaps_delayed_worker.conf`:
|
If your system uses upstart for init scripts, put the following code into `/etc/init/metamaps_delayed_job.conf`:
|
||||||
|
|
||||||
description "Delayed Jobs Worker for Metamaps"
|
description "Delayed Jobs Worker for Metamaps"
|
||||||
|
|
||||||
|
@ -128,3 +147,30 @@ Then start the service and check the last ten lines of the log file to make sure
|
||||||
|
|
||||||
sudo service metamaps_delayed_job start
|
sudo service metamaps_delayed_job start
|
||||||
tail /var/log/upstart/metamaps_delayed_job.log
|
tail /var/log/upstart/metamaps_delayed_job.log
|
||||||
|
|
||||||
|
#### Systemd service for delayed_worker:
|
||||||
|
|
||||||
|
If your system uses systemd for init scripts, ptu the following code into `/etc/systemd/system/metamaps_delayed_job.service`:
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
Description=metamaps delayed job service
|
||||||
|
After=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=/usr/local/rvm/gems/ruby-2.3.0@metamaps/bin/bundle exec rails jobs:work
|
||||||
|
WorkingDirectory=/home/metamaps/metamaps
|
||||||
|
Restart=always
|
||||||
|
User=metamaps
|
||||||
|
Group=metamaps
|
||||||
|
Environment=HOME=/home/metamaps
|
||||||
|
Environment=PATH="/usr/local/rvm/gems/ruby-2.3.0@metamaps/bin:/usr/local/rvm/gems/ruby-2.3.0@global/bin:/usr/local/rvm/rubies/ruby-2.3.0/bin:/usr/local/rvm/bin:/usr/local/bin:/usr/bin:/bin"
|
||||||
|
Environment=GEM_PATH="/usr/local/rvm/gems/ruby-2.3.0@metamaps:/usr/local/rvm/gems/ruby-2.3.0@global"
|
||||||
|
Environment=RAILS_ENV="production"
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
|
||||||
|
Then start the service and check the last ten lines of the log file to make sure it's running OK:
|
||||||
|
|
||||||
|
sudo systemctl start metamaps_delayed_job
|
||||||
|
# ??? how the heck do you check systemd logs??
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
/* global $, ActionCable */
|
/* global $, ActionCable */
|
||||||
|
|
||||||
|
import { indexOf } from 'lodash'
|
||||||
|
|
||||||
import Active from './Active'
|
import Active from './Active'
|
||||||
import Control from './Control'
|
import Control from './Control'
|
||||||
import Create from './Create'
|
import Create from './Create'
|
||||||
|
@ -110,7 +112,7 @@ const Cable = {
|
||||||
if (edge.getData('mappings').length - 1 === 0) {
|
if (edge.getData('mappings').length - 1 === 0) {
|
||||||
Control.hideEdge(edge)
|
Control.hideEdge(edge)
|
||||||
}
|
}
|
||||||
|
|
||||||
var index = indexOf(edge.getData('synapses'), synapse)
|
var index = indexOf(edge.getData('synapses'), synapse)
|
||||||
edge.getData('mappings').splice(index, 1)
|
edge.getData('mappings').splice(index, 1)
|
||||||
edge.getData('synapses').splice(index, 1)
|
edge.getData('synapses').splice(index, 1)
|
||||||
|
@ -128,19 +130,20 @@ const Cable = {
|
||||||
// containing only the information we need to determine whether the active mapper
|
// containing only the information we need to determine whether the active mapper
|
||||||
// can view this topic, then if we determine it can, we make a call for the full model
|
// can view this topic, then if we determine it can, we make a call for the full model
|
||||||
const t = new DataModel.Topic(event.topic)
|
const t = new DataModel.Topic(event.topic)
|
||||||
|
|
||||||
// refactor the heck outta this, its adding wicked wait time
|
|
||||||
var topic, mapping, mapper, cancel
|
|
||||||
function waitThenRenderTopic() {
|
|
||||||
if (topic && mapping && mapper) {
|
|
||||||
Topic.renderTopic(mapping, topic, true)
|
|
||||||
Engine.runLayout()
|
|
||||||
} else if (!cancel) {
|
|
||||||
setTimeout(waitThenRenderTopic, 10)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (t.authorizeToShow(m) && !DataModel.Topics.get(event.topic.id)) {
|
if (t.authorizeToShow(m) && !DataModel.Topics.get(event.topic.id)) {
|
||||||
|
// refactor the heck outta this, its adding wicked wait time
|
||||||
|
var topic, mapping, mapper, cancel
|
||||||
|
|
||||||
|
const waitThenRenderTopic = () => {
|
||||||
|
if (topic && mapping && mapper) {
|
||||||
|
Topic.renderTopic(mapping, topic, true)
|
||||||
|
Engine.runLayout()
|
||||||
|
} else if (!cancel) {
|
||||||
|
setTimeout(waitThenRenderTopic, 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mapper = DataModel.Mappers.get(event.topic.user_id)
|
mapper = DataModel.Mappers.get(event.topic.user_id)
|
||||||
if (mapper === undefined) {
|
if (mapper === undefined) {
|
||||||
Mapper.get(event.topic.user_id, function(m) {
|
Mapper.get(event.topic.user_id, function(m) {
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
/* global $ */
|
|
||||||
|
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import outdent from 'outdent'
|
import outdent from 'outdent'
|
||||||
|
|
||||||
|
@ -8,7 +6,6 @@ import DataModel from './DataModel'
|
||||||
import Engine from './Engine'
|
import Engine from './Engine'
|
||||||
import Filter from './Filter'
|
import Filter from './Filter'
|
||||||
import GlobalUI from './GlobalUI'
|
import GlobalUI from './GlobalUI'
|
||||||
import JIT from './JIT'
|
|
||||||
import Mouse from './Mouse'
|
import Mouse from './Mouse'
|
||||||
import Selected from './Selected'
|
import Selected from './Selected'
|
||||||
import Settings from './Settings'
|
import Settings from './Settings'
|
||||||
|
@ -99,7 +96,6 @@ const Control = {
|
||||||
|
|
||||||
var permToDelete = Active.Mapper.id === topic.get('user_id') || Active.Mapper.get('admin')
|
var permToDelete = Active.Mapper.id === topic.get('user_id') || Active.Mapper.get('admin')
|
||||||
if (permToDelete) {
|
if (permToDelete) {
|
||||||
var mappableid = topic.id
|
|
||||||
var mapping = node.getData('mapping')
|
var mapping = node.getData('mapping')
|
||||||
topic.destroy()
|
topic.destroy()
|
||||||
DataModel.Mappings.remove(mapping)
|
DataModel.Mappings.remove(mapping)
|
||||||
|
@ -149,7 +145,6 @@ const Control = {
|
||||||
}
|
}
|
||||||
|
|
||||||
var topic = node.getData('topic')
|
var topic = node.getData('topic')
|
||||||
var mappableid = topic.id
|
|
||||||
var mapping = node.getData('mapping')
|
var mapping = node.getData('mapping')
|
||||||
mapping.destroy()
|
mapping.destroy()
|
||||||
DataModel.Topics.remove(topic)
|
DataModel.Topics.remove(topic)
|
||||||
|
@ -268,7 +263,6 @@ const Control = {
|
||||||
if (edge.getData('synapses').length - 1 === 0) {
|
if (edge.getData('synapses').length - 1 === 0) {
|
||||||
Control.hideEdge(edge)
|
Control.hideEdge(edge)
|
||||||
}
|
}
|
||||||
var mappableid = synapse.id
|
|
||||||
synapse.destroy()
|
synapse.destroy()
|
||||||
|
|
||||||
// the server will destroy the mapping, we just need to remove it here
|
// the server will destroy the mapping, we just need to remove it here
|
||||||
|
@ -319,7 +313,6 @@ const Control = {
|
||||||
|
|
||||||
var synapse = edge.getData('synapses')[index]
|
var synapse = edge.getData('synapses')[index]
|
||||||
var mapping = edge.getData('mappings')[index]
|
var mapping = edge.getData('mappings')[index]
|
||||||
var mappableid = synapse.id
|
|
||||||
mapping.destroy()
|
mapping.destroy()
|
||||||
|
|
||||||
DataModel.Synapses.remove(synapse)
|
DataModel.Synapses.remove(synapse)
|
||||||
|
|
|
@ -335,18 +335,18 @@ console.log(codesToSwitchToIds)
|
||||||
},
|
},
|
||||||
source: synapseBloodhound
|
source: synapseBloodhound
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'existing_synapses',
|
name: 'existing_synapses',
|
||||||
limit: 50,
|
limit: 50,
|
||||||
display: function(s) { return s.label },
|
display: function(s) { return s.label },
|
||||||
templates: {
|
templates: {
|
||||||
suggestion: function(s) {
|
suggestion: function(s) {
|
||||||
return Hogan.compile($('#synapseAutocompleteTemplate').html()).render(s)
|
return Hogan.compile($('#synapseAutocompleteTemplate').html()).render(s)
|
||||||
},
|
|
||||||
header: '<h3>Existing synapses</h3>'
|
|
||||||
},
|
},
|
||||||
source: existingSynapseBloodhound
|
header: '<h3>Existing synapses</h3>'
|
||||||
}]
|
},
|
||||||
|
source: existingSynapseBloodhound
|
||||||
|
}]
|
||||||
)
|
)
|
||||||
|
|
||||||
$('#synapse_desc').keyup(function(e) {
|
$('#synapse_desc').keyup(function(e) {
|
||||||
|
|
|
@ -7,7 +7,6 @@ try { Backbone.$ = window.$ } catch (err) {}
|
||||||
import Active from '../Active'
|
import Active from '../Active'
|
||||||
import InfoBox from '../Map/InfoBox'
|
import InfoBox from '../Map/InfoBox'
|
||||||
import Mapper from '../Mapper'
|
import Mapper from '../Mapper'
|
||||||
import Realtime from '../Realtime'
|
|
||||||
|
|
||||||
const Map = Backbone.Model.extend({
|
const Map = Backbone.Model.extend({
|
||||||
urlRoot: '/maps',
|
urlRoot: '/maps',
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
/* global $ */
|
|
||||||
|
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import outdent from 'outdent'
|
import outdent from 'outdent'
|
||||||
import Backbone from 'backbone'
|
import Backbone from 'backbone'
|
||||||
|
@ -7,8 +5,6 @@ try { Backbone.$ = window.$ } catch (err) {}
|
||||||
|
|
||||||
import Active from '../Active'
|
import Active from '../Active'
|
||||||
import Filter from '../Filter'
|
import Filter from '../Filter'
|
||||||
import JIT from '../JIT'
|
|
||||||
import Realtime from '../Realtime'
|
|
||||||
import SynapseCard from '../SynapseCard'
|
import SynapseCard from '../SynapseCard'
|
||||||
import Visualize from '../Visualize'
|
import Visualize from '../Visualize'
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
/* global $ */
|
|
||||||
|
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import Backbone from 'backbone'
|
import Backbone from 'backbone'
|
||||||
try { Backbone.$ = window.$ } catch (err) {}
|
try { Backbone.$ = window.$ } catch (err) {}
|
||||||
|
@ -7,8 +5,6 @@ try { Backbone.$ = window.$ } catch (err) {}
|
||||||
import Active from '../Active'
|
import Active from '../Active'
|
||||||
import Engine from '../Engine'
|
import Engine from '../Engine'
|
||||||
import Filter from '../Filter'
|
import Filter from '../Filter'
|
||||||
import JIT from '../JIT'
|
|
||||||
import Realtime from '../Realtime'
|
|
||||||
import TopicCard from '../TopicCard'
|
import TopicCard from '../TopicCard'
|
||||||
import Visualize from '../Visualize'
|
import Visualize from '../Visualize'
|
||||||
|
|
||||||
|
|
|
@ -81,11 +81,11 @@ const DataModel = {
|
||||||
var myCollection = serverData.Mine ? serverData.Mine : []
|
var myCollection = serverData.Mine ? serverData.Mine : []
|
||||||
var sharedCollection = serverData.Shared ? serverData.Shared : []
|
var sharedCollection = serverData.Shared ? serverData.Shared : []
|
||||||
var starredCollection = serverData.Starred ? serverData.Starred : []
|
var starredCollection = serverData.Starred ? serverData.Starred : []
|
||||||
var mapperCollection = []
|
var mapperCollection = serverData.Mapper ? serverData.Mapper : []
|
||||||
var mapperOptionsObj = { id: 'mapper', sortBy: 'updated_at' }
|
var mapperOptionsObj = { id: 'mapper', sortBy: 'updated_at' }
|
||||||
if (self.Maps.Mapper.mapperId) {
|
if (serverData.Mapper && serverData.Mapper.mapperId) {
|
||||||
mapperCollection = serverData.Mapper.models
|
mapperCollection = serverData.Mapper.models
|
||||||
mapperOptionsObj.mapperId = serverData.Mapper.id
|
mapperOptionsObj.mapperId = serverData.Mapper.mapperId
|
||||||
}
|
}
|
||||||
var featuredCollection = serverData.Featured ? serverData.Featured : []
|
var featuredCollection = serverData.Featured ? serverData.Featured : []
|
||||||
var activeCollection = serverData.Active ? serverData.Active : []
|
var activeCollection = serverData.Active ? serverData.Active : []
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
const Debug = (arg = window.Metamaps) => {
|
const Debug = function(arg = window.Metamaps) {
|
||||||
|
if (arg === undefined && typeof window !== 'undefined') arg = window.Metamaps
|
||||||
console.debug(arg)
|
console.debug(arg)
|
||||||
console.debug(`Metamaps Version: ${arg.VERSION}`)
|
console.debug(`Metamaps Version: ${arg.ServerData.VERSION}`)
|
||||||
|
console.debug(`Build: ${arg.ServerData.BUILD}`)
|
||||||
|
console.debug(`Last Updated: ${arg.ServerData.LAST_UPDATED}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Debug
|
export default Debug
|
||||||
|
|
|
@ -7,6 +7,7 @@ import outdent from 'outdent'
|
||||||
import ImportDialogBox from '../../components/ImportDialogBox'
|
import ImportDialogBox from '../../components/ImportDialogBox'
|
||||||
|
|
||||||
import PasteInput from '../PasteInput'
|
import PasteInput from '../PasteInput'
|
||||||
|
import Map from '../Map'
|
||||||
|
|
||||||
const ImportDialog = {
|
const ImportDialog = {
|
||||||
openLightbox: null,
|
openLightbox: null,
|
||||||
|
@ -24,14 +25,19 @@ const ImportDialog = {
|
||||||
`))
|
`))
|
||||||
ReactDOM.render(React.createElement(ImportDialogBox, {
|
ReactDOM.render(React.createElement(ImportDialogBox, {
|
||||||
onFileAdded: PasteInput.handleFile,
|
onFileAdded: PasteInput.handleFile,
|
||||||
exampleImageUrl: serverData['import-example.png']
|
exampleImageUrl: serverData['import-example.png'],
|
||||||
|
downloadScreenshot: ImportDialog.downloadScreenshot
|
||||||
}), $('.importDialogWrapper').get(0))
|
}), $('.importDialogWrapper').get(0))
|
||||||
},
|
},
|
||||||
show: function() {
|
show: function() {
|
||||||
ImportDialog.openLightbox('import-dialog')
|
ImportDialog.openLightbox('import-dialog')
|
||||||
},
|
},
|
||||||
hide: function() {
|
hide: function() {
|
||||||
ImportDialog.closeLightbox('import-dialog')
|
ImportDialog.closeLightbox()
|
||||||
|
},
|
||||||
|
downloadScreenshot: function() {
|
||||||
|
ImportDialog.hide()
|
||||||
|
Map.offerScreenshotDownload()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,9 +12,11 @@ import NotificationIcon from './NotificationIcon'
|
||||||
|
|
||||||
const GlobalUI = {
|
const GlobalUI = {
|
||||||
notifyTimeout: null,
|
notifyTimeout: null,
|
||||||
|
notifyQueue: [],
|
||||||
|
notifying: false,
|
||||||
lightbox: null,
|
lightbox: null,
|
||||||
init: function(serverData) {
|
init: function(serverData) {
|
||||||
var self = GlobalUI
|
const self = GlobalUI
|
||||||
|
|
||||||
self.Search.init(serverData)
|
self.Search.init(serverData)
|
||||||
self.CreateMap.init(serverData)
|
self.CreateMap.init(serverData)
|
||||||
|
@ -45,7 +47,7 @@ const GlobalUI = {
|
||||||
}, 200, 'easeInCubic', function() { $(this).hide() })
|
}, 200, 'easeInCubic', function() { $(this).hide() })
|
||||||
},
|
},
|
||||||
openLightbox: function(which) {
|
openLightbox: function(which) {
|
||||||
var self = GlobalUI
|
const self = GlobalUI
|
||||||
|
|
||||||
$('.lightboxContent').hide()
|
$('.lightboxContent').hide()
|
||||||
$('#' + which).show()
|
$('#' + which).show()
|
||||||
|
@ -72,7 +74,7 @@ const GlobalUI = {
|
||||||
},
|
},
|
||||||
|
|
||||||
closeLightbox: function(event) {
|
closeLightbox: function(event) {
|
||||||
var self = GlobalUI
|
const self = GlobalUI
|
||||||
|
|
||||||
if (event) event.preventDefault()
|
if (event) event.preventDefault()
|
||||||
|
|
||||||
|
@ -96,23 +98,45 @@ const GlobalUI = {
|
||||||
}
|
}
|
||||||
self.lightbox = null
|
self.lightbox = null
|
||||||
},
|
},
|
||||||
notifyUser: function(message, leaveOpen) {
|
notifyUser: function(message, opts = {}) {
|
||||||
var self = GlobalUI
|
const self = GlobalUI
|
||||||
|
|
||||||
|
if (self.notifying) {
|
||||||
|
self.notifyQueue.push({ message, opts })
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
self._notifyUser(message, opts)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// note: use the wrapper function notifyUser instead of this one
|
||||||
|
_notifyUser: function(message, opts = {}) {
|
||||||
|
const self = GlobalUI
|
||||||
|
|
||||||
|
const { leaveOpen = false, timeOut = 8000 } = opts
|
||||||
|
|
||||||
$('#toast').html(message)
|
$('#toast').html(message)
|
||||||
self.showDiv('#toast')
|
self.showDiv('#toast')
|
||||||
clearTimeout(self.notifyTimeOut)
|
clearTimeout(self.notifyTimeOut)
|
||||||
|
|
||||||
if (!leaveOpen) {
|
if (!leaveOpen) {
|
||||||
self.notifyTimeOut = setTimeout(function() {
|
self.notifyTimeOut = setTimeout(function() {
|
||||||
self.hideDiv('#toast')
|
GlobalUI.clearNotify()
|
||||||
}, 8000)
|
}, timeOut)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.notifying = true
|
||||||
},
|
},
|
||||||
clearNotify: function() {
|
clearNotify: function() {
|
||||||
var self = GlobalUI
|
const self = GlobalUI
|
||||||
|
|
||||||
clearTimeout(self.notifyTimeOut)
|
// if there are messages remaining, display them
|
||||||
self.hideDiv('#toast')
|
if (self.notifyQueue.length > 0) {
|
||||||
|
const { message, opts } = self.notifyQueue.shift()
|
||||||
|
self._notifyUser(message, opts)
|
||||||
|
} else {
|
||||||
|
self.hideDiv('#toast')
|
||||||
|
self.notifying = false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
shareInvite: function(inviteLink) {
|
shareInvite: function(inviteLink) {
|
||||||
clipboard.copy({
|
clipboard.copy({
|
||||||
|
|
|
@ -374,7 +374,7 @@ const Import = {
|
||||||
$.get('/hacks/load_url_title', {
|
$.get('/hacks/load_url_title', {
|
||||||
url
|
url
|
||||||
}, function success(data, textStatus) {
|
}, function success(data, textStatus) {
|
||||||
if (data.trim() === '') return
|
if (typeof data === 'string' && data.trim() === '') return
|
||||||
var selector = '#showcard #topic_' + topic.get('id') + ' .best_in_place'
|
var selector = '#showcard #topic_' + topic.get('id') + ' .best_in_place'
|
||||||
if ($(selector).find('form').length > 0) {
|
if ($(selector).find('form').length > 0) {
|
||||||
$(selector).find('textarea, input').val(data.title)
|
$(selector).find('textarea, input').val(data.title)
|
||||||
|
|
|
@ -20,7 +20,7 @@ const Listeners = {
|
||||||
if (!(Active.Map || Active.Topic)) return
|
if (!(Active.Map || Active.Topic)) return
|
||||||
|
|
||||||
const onCanvas = e.target.tagName === 'BODY'
|
const onCanvas = e.target.tagName === 'BODY'
|
||||||
|
|
||||||
switch (e.which) {
|
switch (e.which) {
|
||||||
case 13: // if enter key is pressed
|
case 13: // if enter key is pressed
|
||||||
// prevent topic creation if sending a message
|
// prevent topic creation if sending a message
|
||||||
|
@ -31,6 +31,10 @@ const Listeners = {
|
||||||
case 27: // if esc key is pressed
|
case 27: // if esc key is pressed
|
||||||
JIT.escKeyHandler()
|
JIT.escKeyHandler()
|
||||||
break
|
break
|
||||||
|
case 46: // if DEL is pressed
|
||||||
|
e.preventDefault()
|
||||||
|
Control.deleteSelected()
|
||||||
|
break
|
||||||
case 65: // if a or A is pressed
|
case 65: // if a or A is pressed
|
||||||
if ((e.ctrlKey || e.metaKey) && onCanvas) {
|
if ((e.ctrlKey || e.metaKey) && onCanvas) {
|
||||||
const nodesCount = Object.keys(Visualize.mGraph.graph.nodes).length
|
const nodesCount = Object.keys(Visualize.mGraph.graph.nodes).length
|
||||||
|
@ -124,7 +128,6 @@ const Listeners = {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
$(window).resize(function() {
|
$(window).resize(function() {
|
||||||
if (Visualize && Visualize.mGraph) {
|
if (Visualize && Visualize.mGraph) {
|
||||||
Util.resizeCanvas(Visualize.mGraph.canvas)
|
Util.resizeCanvas(Visualize.mGraph.canvas)
|
||||||
|
|
|
@ -35,9 +35,11 @@ const InfoBox = {
|
||||||
data-bip-value="{{desc}}"
|
data-bip-value="{{desc}}"
|
||||||
>{{desc}}</span>`,
|
>{{desc}}</span>`,
|
||||||
userImageUrl: '',
|
userImageUrl: '',
|
||||||
init: function(serverData) {
|
init: function(serverData, updateThumbnail) {
|
||||||
var self = InfoBox
|
var self = InfoBox
|
||||||
|
|
||||||
|
self.updateThumbnail = updateThumbnail
|
||||||
|
|
||||||
$('.mapInfoIcon').click(self.toggleBox)
|
$('.mapInfoIcon').click(self.toggleBox)
|
||||||
$('.mapInfoBox').click(function(event) {
|
$('.mapInfoBox').click(function(event) {
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
|
@ -181,6 +183,7 @@ const InfoBox = {
|
||||||
$('.mapInfoBox.yourMap').unbind('.yourMap').bind('click.yourMap', self.hidePermissionSelect)
|
$('.mapInfoBox.yourMap').unbind('.yourMap').bind('click.yourMap', self.hidePermissionSelect)
|
||||||
|
|
||||||
$('.yourMap .mapInfoDelete').unbind().click(self.deleteActiveMap)
|
$('.yourMap .mapInfoDelete').unbind().click(self.deleteActiveMap)
|
||||||
|
$('.mapInfoThumbnail').unbind().click(self.updateThumbnail)
|
||||||
|
|
||||||
$('.mapContributors span, #mapContribs').unbind().click(function(event) {
|
$('.mapContributors span, #mapContribs').unbind().click(function(event) {
|
||||||
$('.mapContributors .tip').toggle()
|
$('.mapContributors .tip').toggle()
|
||||||
|
|
|
@ -46,7 +46,10 @@ const Map = {
|
||||||
GlobalUI.CreateMap.emptyForkMapForm = $('#fork_map').html()
|
GlobalUI.CreateMap.emptyForkMapForm = $('#fork_map').html()
|
||||||
|
|
||||||
self.updateStar()
|
self.updateStar()
|
||||||
InfoBox.init(serverData)
|
|
||||||
|
InfoBox.init(serverData, function updateThumbnail() {
|
||||||
|
self.uploadMapScreenshot()
|
||||||
|
})
|
||||||
CheatSheet.init(serverData)
|
CheatSheet.init(serverData)
|
||||||
|
|
||||||
$('.viewOnly .requestAccess').click(self.requestAccess)
|
$('.viewOnly .requestAccess').click(self.requestAccess)
|
||||||
|
@ -253,6 +256,48 @@ const Map = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
exportImage: function() {
|
exportImage: function() {
|
||||||
|
Map.uploadMapScreenshot()
|
||||||
|
Map.offerScreenshotDownload()
|
||||||
|
GlobalUI.notifyUser('Note: this button is going away. Check the map card or the import box for setting the map thumbnail or downloading a screenshot.')
|
||||||
|
},
|
||||||
|
offerScreenshotDownload: () => {
|
||||||
|
const canvas = Map.getMapCanvasForScreenshots()
|
||||||
|
const filename = Map.getMapScreenshotFilename(Active.Map)
|
||||||
|
|
||||||
|
var downloadMessage = outdent`
|
||||||
|
Captured map screenshot!
|
||||||
|
<a id="map-screenshot-download-link"
|
||||||
|
href="${canvas.canvas.toDataURL()}"
|
||||||
|
download="${filename}"
|
||||||
|
>
|
||||||
|
DOWNLOAD
|
||||||
|
</a>`
|
||||||
|
GlobalUI.notifyUser(downloadMessage)
|
||||||
|
},
|
||||||
|
uploadMapScreenshot: () => {
|
||||||
|
const canvas = Map.getMapCanvasForScreenshots()
|
||||||
|
const filename = Map.getMapScreenshotFilename(Active.Map)
|
||||||
|
|
||||||
|
canvas.canvas.toBlob(imageBlob => {
|
||||||
|
const formData = new window.FormData()
|
||||||
|
formData.append('map[screenshot]', imageBlob, filename)
|
||||||
|
$.ajax({
|
||||||
|
type: 'PATCH',
|
||||||
|
dataType: 'json',
|
||||||
|
url: `/maps/${Active.Map.id}`,
|
||||||
|
data: formData,
|
||||||
|
processData: false,
|
||||||
|
contentType: false,
|
||||||
|
success: function(data) {
|
||||||
|
GlobalUI.notifyUser('Successfully updated map screenshot.')
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
GlobalUI.notifyUser('Failed to update map screenshot.')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getMapCanvasForScreenshots: () => {
|
||||||
var canvas = {}
|
var canvas = {}
|
||||||
|
|
||||||
canvas.canvas = document.createElement('canvas')
|
canvas.canvas = document.createElement('canvas')
|
||||||
|
@ -338,8 +383,9 @@ const Map = {
|
||||||
node.visited = !T
|
node.visited = !T
|
||||||
})
|
})
|
||||||
|
|
||||||
var map = Active.Map
|
return canvas
|
||||||
|
},
|
||||||
|
getMapScreenshotFilename: map => {
|
||||||
var today = new Date()
|
var today = new Date()
|
||||||
var dd = today.getDate()
|
var dd = today.getDate()
|
||||||
var mm = today.getMonth() + 1 // January is 0!
|
var mm = today.getMonth() + 1 // January is 0!
|
||||||
|
@ -354,30 +400,7 @@ const Map = {
|
||||||
|
|
||||||
var mapName = map.get('name').split(' ').join(['-'])
|
var mapName = map.get('name').split(' ').join(['-'])
|
||||||
const filename = `metamap-${map.id}-${mapName}-${today}.png`
|
const filename = `metamap-${map.id}-${mapName}-${today}.png`
|
||||||
|
return filename
|
||||||
var downloadMessage = outdent`
|
|
||||||
Captured map screenshot!
|
|
||||||
<a href="${canvas.canvas.toDataURL()}" download="${filename}">DOWNLOAD</a>`
|
|
||||||
GlobalUI.notifyUser(downloadMessage)
|
|
||||||
|
|
||||||
canvas.canvas.toBlob(imageBlob => {
|
|
||||||
const formData = new window.FormData()
|
|
||||||
formData.append('map[screenshot]', imageBlob, filename)
|
|
||||||
$.ajax({
|
|
||||||
type: 'PATCH',
|
|
||||||
dataType: 'json',
|
|
||||||
url: `/maps/${map.id}`,
|
|
||||||
data: formData,
|
|
||||||
processData: false,
|
|
||||||
contentType: false,
|
|
||||||
success: function(data) {
|
|
||||||
console.log('successfully uploaded map screenshot')
|
|
||||||
},
|
|
||||||
error: function() {
|
|
||||||
console.log('failed to save map screenshot')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import Visualize from './Visualize'
|
||||||
|
|
||||||
const PasteInput = {
|
const PasteInput = {
|
||||||
// thanks to https://github.com/kevva/url-regex
|
// thanks to https://github.com/kevva/url-regex
|
||||||
|
// eslint-disable-next-line no-useless-escape
|
||||||
URL_REGEX: new RegExp('^(?:(?:(?:[a-z]+:)?//)|www\.)(?:\S+(?::\S*)?@)?(?:localhost|(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(?:\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])){3}|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,}))\.?)(?::\d{2,5})?(?:[/?#][^\s"]*)?$'),
|
URL_REGEX: new RegExp('^(?:(?:(?:[a-z]+:)?//)|www\.)(?:\S+(?::\S*)?@)?(?:localhost|(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(?:\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])){3}|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,}))\.?)(?::\d{2,5})?(?:[/?#][^\s"]*)?$'),
|
||||||
|
|
||||||
init: function() {
|
init: function() {
|
||||||
|
|
|
@ -14,7 +14,7 @@ module.exports = {
|
||||||
SEND_COORDS: 'SEND_COORDS',
|
SEND_COORDS: 'SEND_COORDS',
|
||||||
DRAG_TOPIC: 'DRAG_TOPIC',
|
DRAG_TOPIC: 'DRAG_TOPIC',
|
||||||
|
|
||||||
/* EVENTS RECEIVABLE FROM NODE SERVER*/
|
/* EVENTS RECEIVABLE FROM NODE SERVER */
|
||||||
JUNTO_UPDATED: 'JUNTO_UPDATED',
|
JUNTO_UPDATED: 'JUNTO_UPDATED',
|
||||||
INVITED_TO_CALL: 'INVITED_TO_CALL',
|
INVITED_TO_CALL: 'INVITED_TO_CALL',
|
||||||
INVITED_TO_JOIN: 'INVITED_TO_JOIN',
|
INVITED_TO_JOIN: 'INVITED_TO_JOIN',
|
||||||
|
@ -29,5 +29,5 @@ module.exports = {
|
||||||
NEW_MAPPER: 'NEW_MAPPER',
|
NEW_MAPPER: 'NEW_MAPPER',
|
||||||
LOST_MAPPER: 'LOST_MAPPER',
|
LOST_MAPPER: 'LOST_MAPPER',
|
||||||
TOPIC_DRAGGED: 'TOPIC_DRAGGED',
|
TOPIC_DRAGGED: 'TOPIC_DRAGGED',
|
||||||
PEER_COORDS_UPDATED: 'PEER_COORDS_UPDATED',
|
PEER_COORDS_UPDATED: 'PEER_COORDS_UPDATED'
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,7 @@ import Create from '../Create'
|
||||||
import DataModel from '../DataModel'
|
import DataModel from '../DataModel'
|
||||||
import JIT from '../JIT'
|
import JIT from '../JIT'
|
||||||
import Util from '../Util'
|
import Util from '../Util'
|
||||||
import Views from '../Views'
|
import Views, { ChatView } from '../Views'
|
||||||
import { ChatView } from '../Views'
|
|
||||||
import Visualize from '../Visualize'
|
import Visualize from '../Visualize'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -153,7 +152,7 @@ let Realtime = {
|
||||||
config: { DOUBLE_CLICK_TOLERANCE: 200 }
|
config: { DOUBLE_CLICK_TOLERANCE: 200 }
|
||||||
})
|
})
|
||||||
self.room.videoAdded(self.handleVideoAdded)
|
self.room.videoAdded(self.handleVideoAdded)
|
||||||
|
|
||||||
self.startActiveMap()
|
self.startActiveMap()
|
||||||
} // if Active.Mapper
|
} // if Active.Mapper
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,18 +4,12 @@
|
||||||
everthing in this file happens as a result of websocket events
|
everthing in this file happens as a result of websocket events
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { indexOf } from 'lodash'
|
|
||||||
|
|
||||||
import { JUNTO_UPDATED } from './events'
|
import { JUNTO_UPDATED } from './events'
|
||||||
|
|
||||||
import Active from '../Active'
|
import Active from '../Active'
|
||||||
import { ChatView } from '../Views'
|
import { ChatView } from '../Views'
|
||||||
import DataModel from '../DataModel'
|
import DataModel from '../DataModel'
|
||||||
import GlobalUI from '../GlobalUI'
|
import GlobalUI from '../GlobalUI'
|
||||||
import Control from '../Control'
|
|
||||||
import Mapper from '../Mapper'
|
|
||||||
import Topic from '../Topic'
|
|
||||||
import Synapse from '../Synapse'
|
|
||||||
import Util from '../Util'
|
import Util from '../Util'
|
||||||
import Visualize from '../Visualize'
|
import Visualize from '../Visualize'
|
||||||
|
|
||||||
|
@ -155,7 +149,7 @@ export const invitedToCall = self => inviter => {
|
||||||
notifyText += username + ' is inviting you to a conversation. Join live?'
|
notifyText += username + ' is inviting you to a conversation. Join live?'
|
||||||
notifyText += ' <button type="button" class="toast-button button yes">Yes</button>'
|
notifyText += ' <button type="button" class="toast-button button yes">Yes</button>'
|
||||||
notifyText += ' <button type="button" class="toast-button button btn-no no">No</button>'
|
notifyText += ' <button type="button" class="toast-button button btn-no no">No</button>'
|
||||||
GlobalUI.notifyUser(notifyText, true)
|
GlobalUI.notifyUser(notifyText, { leaveOpen: true })
|
||||||
$('#toast button.yes').click(e => self.acceptCall(inviter))
|
$('#toast button.yes').click(e => self.acceptCall(inviter))
|
||||||
$('#toast button.no').click(e => self.denyCall(inviter))
|
$('#toast button.no').click(e => self.denyCall(inviter))
|
||||||
}
|
}
|
||||||
|
@ -168,7 +162,7 @@ export const invitedToJoin = self => inviter => {
|
||||||
var notifyText = username + ' is inviting you to the conversation. Join?'
|
var notifyText = username + ' is inviting you to the conversation. Join?'
|
||||||
notifyText += ' <button type="button" class="toast-button button yes">Yes</button>'
|
notifyText += ' <button type="button" class="toast-button button yes">Yes</button>'
|
||||||
notifyText += ' <button type="button" class="toast-button button btn-no no">No</button>'
|
notifyText += ' <button type="button" class="toast-button button btn-no no">No</button>'
|
||||||
GlobalUI.notifyUser(notifyText, true)
|
GlobalUI.notifyUser(notifyText, { leaveOpen: true })
|
||||||
$('#toast button.yes').click(e => self.joinCall())
|
$('#toast button.yes').click(e => self.joinCall())
|
||||||
$('#toast button.no').click(e => self.denyInvite(inviter))
|
$('#toast button.no').click(e => self.denyInvite(inviter))
|
||||||
}
|
}
|
||||||
|
@ -207,10 +201,10 @@ export const callInProgress = self => () => {
|
||||||
var notifyText = "There's a conversation happening, want to join?"
|
var notifyText = "There's a conversation happening, want to join?"
|
||||||
notifyText += ' <button type="button" class="toast-button button yes">Yes</button>'
|
notifyText += ' <button type="button" class="toast-button button yes">Yes</button>'
|
||||||
notifyText += ' <button type="button" class="toast-button button btn-no no">No</button>'
|
notifyText += ' <button type="button" class="toast-button button btn-no no">No</button>'
|
||||||
GlobalUI.notifyUser(notifyText, true)
|
GlobalUI.notifyUser(notifyText, { leaveOpen: true })
|
||||||
$('#toast button.yes').click(e => self.joinCall())
|
$('#toast button.yes').click(e => self.joinCall())
|
||||||
$('#toast button.no').click(e => GlobalUI.clearNotify())
|
$('#toast button.no').click(e => GlobalUI.clearNotify())
|
||||||
ChatView.conversationInProgress()
|
ChatView.conversationInProgress()
|
||||||
}
|
}
|
||||||
|
|
||||||
export const callStarted = self => () => {
|
export const callStarted = self => () => {
|
||||||
|
@ -218,7 +212,7 @@ export const callStarted = self => () => {
|
||||||
var notifyText = "There's a conversation starting, want to join?"
|
var notifyText = "There's a conversation starting, want to join?"
|
||||||
notifyText += ' <button type="button" class="toast-button button">Yes</button>'
|
notifyText += ' <button type="button" class="toast-button button">Yes</button>'
|
||||||
notifyText += ' <button type="button" class="toast-button button btn-no">No</button>'
|
notifyText += ' <button type="button" class="toast-button button btn-no">No</button>'
|
||||||
GlobalUI.notifyUser(notifyText, true)
|
GlobalUI.notifyUser(notifyText, { leaveOpen: true })
|
||||||
$('#toast button.yes').click(e => self.joinCall())
|
$('#toast button.yes').click(e => self.joinCall())
|
||||||
$('#toast button.no').click(e => GlobalUI.clearNotify())
|
$('#toast button.no').click(e => GlobalUI.clearNotify())
|
||||||
ChatView.conversationInProgress()
|
ChatView.conversationInProgress()
|
||||||
|
|
|
@ -39,7 +39,7 @@ const _Router = Backbone.Router.extend({
|
||||||
|
|
||||||
var navigate = function() {
|
var navigate = function() {
|
||||||
self.timeoutId = setTimeout(function() {
|
self.timeoutId = setTimeout(function() {
|
||||||
self.navigate('')
|
self.navigateAndTrack('')
|
||||||
}, 300)
|
}, 300)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,7 +121,7 @@ const _Router = Backbone.Router.extend({
|
||||||
path += '/' + DataModel.Maps.Mapper.mapperId
|
path += '/' + DataModel.Maps.Mapper.mapperId
|
||||||
}
|
}
|
||||||
|
|
||||||
self.navigate(path)
|
self.navigateAndTrack(path)
|
||||||
}
|
}
|
||||||
var navigateTimeout = function() {
|
var navigateTimeout = function() {
|
||||||
self.timeoutId = setTimeout(navigate, 300)
|
self.timeoutId = setTimeout(navigate, 300)
|
||||||
|
@ -202,6 +202,11 @@ const _Router = Backbone.Router.extend({
|
||||||
|
|
||||||
const Router = new _Router()
|
const Router = new _Router()
|
||||||
|
|
||||||
|
Router.navigateAndTrack = (fragment, options) => {
|
||||||
|
Router.navigate(fragment, options)
|
||||||
|
window.ga && window.ga('send', 'pageview', location.pathname, {title: document.title})
|
||||||
|
}
|
||||||
|
|
||||||
Router.intercept = function(evt) {
|
Router.intercept = function(evt) {
|
||||||
var segments
|
var segments
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,14 @@
|
||||||
/* global $ */
|
/* global $ */
|
||||||
|
|
||||||
import { Parser, HtmlRenderer } from 'commonmark'
|
import { Parser, HtmlRenderer } from 'commonmark'
|
||||||
|
import { emojiIndex } from 'emoji-mart'
|
||||||
|
import { escapeRegExp } from 'lodash'
|
||||||
|
|
||||||
|
const emojiToShortcodes = {}
|
||||||
|
Object.keys(emojiIndex.emojis).forEach(key => {
|
||||||
|
const emoji = emojiIndex.emojis[key]
|
||||||
|
emojiToShortcodes[emoji.native] = emoji.colons
|
||||||
|
})
|
||||||
|
|
||||||
const Util = {
|
const Util = {
|
||||||
// helper function to determine how many lines are needed
|
// helper function to determine how many lines are needed
|
||||||
|
@ -150,6 +158,29 @@ const Util = {
|
||||||
canvas.scale(oldAttr.scaleX, oldAttr.scaleY)
|
canvas.scale(oldAttr.scaleX, oldAttr.scaleY)
|
||||||
const newAttr = Util.logCanvasAttributes(canvas)
|
const newAttr = Util.logCanvasAttributes(canvas)
|
||||||
canvas.translate(newAttr.centreCoords.x - oldAttr.centreCoords.x, newAttr.centreCoords.y - oldAttr.centreCoords.y)
|
canvas.translate(newAttr.centreCoords.x - oldAttr.centreCoords.x, newAttr.centreCoords.y - oldAttr.centreCoords.y)
|
||||||
|
},
|
||||||
|
removeEmoji: function(withEmoji) {
|
||||||
|
let text = withEmoji
|
||||||
|
Object.keys(emojiIndex.emojis).forEach(key => {
|
||||||
|
const emoji = emojiIndex.emojis[key]
|
||||||
|
text = text.replace(new RegExp(escapeRegExp(emoji.native), 'g'), emoji.colons)
|
||||||
|
})
|
||||||
|
return text
|
||||||
|
},
|
||||||
|
addEmoji: function(withoutEmoji, opts = { emoticons: true }) {
|
||||||
|
let text = withoutEmoji
|
||||||
|
Object.keys(emojiIndex.emojis).forEach(key => {
|
||||||
|
const emoji = emojiIndex.emojis[key]
|
||||||
|
text = text.replace(new RegExp(escapeRegExp(emoji.colons), 'g'), emoji.native)
|
||||||
|
})
|
||||||
|
if (opts.emoticons) {
|
||||||
|
Object.keys(emojiIndex.emoticons).forEach(emoticon => {
|
||||||
|
const key = emojiIndex.emoticons[emoticon]
|
||||||
|
const emoji = emojiIndex.emojis[key]
|
||||||
|
text = text.replace(new RegExp(escapeRegExp(emoticon), 'g'), emoji.native)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,7 @@ const ChatView = {
|
||||||
$('#' + ChatView.domId).hide()
|
$('#' + ChatView.domId).hide()
|
||||||
},
|
},
|
||||||
render: () => {
|
render: () => {
|
||||||
if (!Active.Map) return
|
if (!Active.Map) return
|
||||||
const self = ChatView
|
const self = ChatView
|
||||||
self.mapChat = ReactDOM.render(React.createElement(MapChat, {
|
self.mapChat = ReactDOM.render(React.createElement(MapChat, {
|
||||||
conversationLive: self.conversationLive,
|
conversationLive: self.conversationLive,
|
||||||
|
@ -111,7 +111,7 @@ const ChatView = {
|
||||||
conversationInProgress: participating => {
|
conversationInProgress: participating => {
|
||||||
ChatView.conversationLive = true
|
ChatView.conversationLive = true
|
||||||
ChatView.isParticipating = participating
|
ChatView.isParticipating = participating
|
||||||
ChatView.render()
|
ChatView.render()
|
||||||
},
|
},
|
||||||
conversationEnded: () => {
|
conversationEnded: () => {
|
||||||
ChatView.conversationLive = false
|
ChatView.conversationLive = false
|
||||||
|
@ -144,7 +144,7 @@ const ChatView = {
|
||||||
},
|
},
|
||||||
addMessage: (message, isInitial, wasMe) => {
|
addMessage: (message, isInitial, wasMe) => {
|
||||||
const self = ChatView
|
const self = ChatView
|
||||||
if (!isInitial) self.mapChat.newMessage()
|
if (!isInitial) self.mapChat.newMessage()
|
||||||
if (!wasMe && !isInitial && self.alertSound) self.sound.play('receivechat')
|
if (!wasMe && !isInitial && self.alertSound) self.sound.play('receivechat')
|
||||||
self.messages.add(message)
|
self.messages.add(message)
|
||||||
self.render()
|
self.render()
|
||||||
|
|
|
@ -1,13 +1,7 @@
|
||||||
/* global $ */
|
/* global $ */
|
||||||
|
|
||||||
import Backbone from 'backbone'
|
|
||||||
import attachMediaStream from 'attachmediastream'
|
import attachMediaStream from 'attachmediastream'
|
||||||
|
|
||||||
// TODO is this line good or bad
|
|
||||||
// Backbone.$ = window.$
|
|
||||||
|
|
||||||
import Active from '../Active'
|
|
||||||
import DataModel from '../DataModel'
|
|
||||||
import Realtime from '../Realtime'
|
import Realtime from '../Realtime'
|
||||||
import VideoView from './VideoView'
|
import VideoView from './VideoView'
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { JUNTO_UPDATED } from '../Realtime/events'
|
||||||
const Views = {
|
const Views = {
|
||||||
init: (serverData) => {
|
init: (serverData) => {
|
||||||
$(document).on(JUNTO_UPDATED, () => ExploreMaps.render())
|
$(document).on(JUNTO_UPDATED, () => ExploreMaps.render())
|
||||||
ChatView.init([serverData['sounds/MM_sounds.mp3'],serverData['sounds/MM_sounds.ogg']])
|
ChatView.init([serverData['sounds/MM_sounds.mp3'], serverData['sounds/MM_sounds.ogg']])
|
||||||
},
|
},
|
||||||
ExploreMaps,
|
ExploreMaps,
|
||||||
ChatView,
|
ChatView,
|
||||||
|
|
|
@ -154,8 +154,6 @@ const Visualize = {
|
||||||
self.mGraph.graph.empty()
|
self.mGraph.graph.empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self.type === 'ForceDirected' && Active.Mapper) $.post('/maps/' + Active.Map.id + '/events/user_presence')
|
|
||||||
|
|
||||||
function runAnimation() {
|
function runAnimation() {
|
||||||
Loading.hide()
|
Loading.hide()
|
||||||
$('#new_topic').show()
|
$('#new_topic').show()
|
||||||
|
@ -216,9 +214,9 @@ const Visualize = {
|
||||||
var t = Active.Topic
|
var t = Active.Topic
|
||||||
|
|
||||||
if (m && window.location.pathname !== '/maps/' + m.id) {
|
if (m && window.location.pathname !== '/maps/' + m.id) {
|
||||||
Router.navigate('/maps/' + m.id)
|
Router.navigateAndTrack('/maps/' + m.id)
|
||||||
} else if (t && window.location.pathname !== '/topics/' + t.id) {
|
} else if (t && window.location.pathname !== '/topics/' + t.id) {
|
||||||
Router.navigate('/topics/' + t.id)
|
Router.navigateAndTrack('/topics/' + t.id)
|
||||||
}
|
}
|
||||||
}, 800)
|
}, 800)
|
||||||
},
|
},
|
||||||
|
|
|
@ -6,7 +6,6 @@ class ImportDialogBox extends Component {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
showImportInstructions: false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,21 +14,9 @@ class ImportDialogBox extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFile = (files, e) => {
|
handleFile = (files, e) => {
|
||||||
// // for some reason it uploads twice, so we need this debouncer
|
|
||||||
// // eslint-disable-next-line no-return-assign
|
|
||||||
// this.debouncer = this.debouncer || window.setTimeout(() => this.debouncer = null, 10)
|
|
||||||
// if (!this.debouncer) {
|
|
||||||
// this.props.onFileAdded(files[0])
|
|
||||||
// }
|
|
||||||
this.props.onFileAdded(files[0])
|
this.props.onFileAdded(files[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleShowInstructions = e => {
|
|
||||||
this.setState({
|
|
||||||
showImportInstructions: !this.state.showImportInstructions
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
render = () => {
|
render = () => {
|
||||||
return (
|
return (
|
||||||
<div className="import-dialog">
|
<div className="import-dialog">
|
||||||
|
@ -40,6 +27,9 @@ class ImportDialogBox extends Component {
|
||||||
<div className="import-blue-button" onClick={this.handleExport('json')}>
|
<div className="import-blue-button" onClick={this.handleExport('json')}>
|
||||||
Export as JSON
|
Export as JSON
|
||||||
</div>
|
</div>
|
||||||
|
<div className="import-blue-button" onClick={this.props.downloadScreenshot}>
|
||||||
|
Download screenshot
|
||||||
|
</div>
|
||||||
<h3>IMPORT</h3>
|
<h3>IMPORT</h3>
|
||||||
<p>To upload a file, drop it here:</p>
|
<p>To upload a file, drop it here:</p>
|
||||||
<Dropzone onDropAccepted={this.handleFile}
|
<Dropzone onDropAccepted={this.handleFile}
|
||||||
|
@ -47,23 +37,7 @@ class ImportDialogBox extends Component {
|
||||||
>
|
>
|
||||||
Drop files here!
|
Drop files here!
|
||||||
</Dropzone>
|
</Dropzone>
|
||||||
<p>
|
<p>See <a href="https://docs.metamaps.cc/importing_and_exporting_data.html">docs.metamaps.cc</a> for instructions.</p>
|
||||||
<a onClick={this.toggleShowInstructions} style={{ textDecoration: 'underline', cursor: 'pointer' }}>
|
|
||||||
Show/hide import instructions
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
{!this.state.showImportInstructions ? null : (<div>
|
|
||||||
<p>
|
|
||||||
You can import topics and synapses by uploading a spreadsheet here.
|
|
||||||
The file should be in comma-separated format (when you save, change the
|
|
||||||
filetype from .xls to .csv).
|
|
||||||
</p>
|
|
||||||
<img src={this.props.exampleImageUrl} style={{ width: '100%' }} />
|
|
||||||
<p style={{ marginTop: '1em' }}>You can choose which columns to include in your data. Topics must have a name field. Synapses must have Topic 1 and Topic 2.</p>
|
|
||||||
<p> </p>
|
|
||||||
<p> * There are many valid import formats. Try exporting a map to see what columns you can include in your import data. You can also copy-paste from Excel to import, or import JSON.</p>
|
|
||||||
<p> * If you are importing a list of links, you can use a Link column in place of the Name column.</p>
|
|
||||||
</div>)}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -71,7 +45,8 @@ class ImportDialogBox extends Component {
|
||||||
|
|
||||||
ImportDialogBox.propTypes = {
|
ImportDialogBox.propTypes = {
|
||||||
onFileAdded: PropTypes.func,
|
onFileAdded: PropTypes.func,
|
||||||
exampleImageUrl: PropTypes.string
|
exampleImageUrl: PropTypes.string,
|
||||||
|
downloadScreenshot: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ImportDialogBox
|
export default ImportDialogBox
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import Autolinker from 'autolinker'
|
import Autolinker from 'autolinker'
|
||||||
|
import Util from '../../Metamaps/Util'
|
||||||
|
|
||||||
const linker = new Autolinker({ newWindow: true, truncate: 50, email: false, phone: false })
|
const linker = new Autolinker({ newWindow: true, truncate: 50, email: false, phone: false })
|
||||||
|
|
||||||
|
@ -10,24 +11,25 @@ function addZero(i) {
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatDate(created_at) {
|
function formatDate(createdAt) {
|
||||||
let date = new Date(created_at)
|
let date = new Date(createdAt)
|
||||||
let formatted = (date.getMonth() + 1) + '/' + date.getDate()
|
let formatted = (date.getMonth() + 1) + '/' + date.getDate()
|
||||||
formatted += ' ' + addZero(date.getHours()) + ':' + addZero(date.getMinutes())
|
formatted += ' ' + addZero(date.getHours()) + ':' + addZero(date.getMinutes())
|
||||||
return formatted
|
return formatted
|
||||||
}
|
}
|
||||||
|
|
||||||
const Message = props => {
|
const Message = props => {
|
||||||
const { user_image, user_name, message, created_at, heading } = props
|
const { user_image: userImage, user_name: userName, message, created_at: createdAt, heading } = props
|
||||||
const messageHtml = {__html: linker.link(message)}
|
const messageHtml = {__html: linker.link(Util.addEmoji(message, { emoticons: false }))}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="chat-message">
|
<div className="chat-message">
|
||||||
<div className="chat-message-user">
|
<div className="chat-message-user">
|
||||||
{heading && <img src={user_image} />}
|
{heading && <img src={userImage} />}
|
||||||
</div>
|
</div>
|
||||||
{heading && <div className="chat-message-meta">
|
{heading && <div className="chat-message-meta">
|
||||||
<span className='chat-message-username'>{user_name}</span>
|
<span className='chat-message-username'>{userName}</span>
|
||||||
<span className='chat-message-time'>{formatDate(created_at)}</span>
|
<span className='chat-message-time'>{formatDate(createdAt)}</span>
|
||||||
</div>}
|
</div>}
|
||||||
<div className="chat-message-text" dangerouslySetInnerHTML={messageHtml}></div>
|
<div className="chat-message-text" dangerouslySetInnerHTML={messageHtml}></div>
|
||||||
<div className="clearfloat"></div>
|
<div className="clearfloat"></div>
|
||||||
|
|
65
frontend/src/components/MapChat/NewMessage.js
Normal file
65
frontend/src/components/MapChat/NewMessage.js
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
import React, { PropTypes, Component } from 'react'
|
||||||
|
import { Emoji, Picker } from 'emoji-mart'
|
||||||
|
|
||||||
|
class NewMessage extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
showEmojiPicker: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleEmojiPicker = () => {
|
||||||
|
this.setState({ showEmojiPicker: !this.state.showEmojiPicker })
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClick = (emoji, event) => {
|
||||||
|
const { messageText } = this.props
|
||||||
|
this.props.handleChange({ target: {
|
||||||
|
value: messageText + emoji.colons
|
||||||
|
}})
|
||||||
|
|
||||||
|
this.setState({ showEmojiPicker: false })
|
||||||
|
this.props.focusMessageInput()
|
||||||
|
}
|
||||||
|
|
||||||
|
render = () => {
|
||||||
|
return (
|
||||||
|
<div className="new-message-area">
|
||||||
|
<Picker set="emojione"
|
||||||
|
onClick={this.handleClick}
|
||||||
|
style={{
|
||||||
|
display: this.state.showEmojiPicker ? 'block' : 'none',
|
||||||
|
maxWidth: '100%'
|
||||||
|
}}
|
||||||
|
emoji="upside_down_face"
|
||||||
|
title="Emoji"
|
||||||
|
/>
|
||||||
|
<div className="extra-message-options">
|
||||||
|
<span className="emoji-picker-button" onClick={this.toggleEmojiPicker}><Emoji size={24} emoji="upside_down_face" /></span>
|
||||||
|
</div>
|
||||||
|
<textarea value={this.props.messageText}
|
||||||
|
onChange={this.props.handleChange}
|
||||||
|
{...this.props.textAreaProps}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NewMessage.propTypes = {
|
||||||
|
messageText: PropTypes.string,
|
||||||
|
handleChange: PropTypes.func,
|
||||||
|
focusMessageInput: PropTypes.func,
|
||||||
|
textAreaProps: PropTypes.shape({
|
||||||
|
className: PropTypes.string,
|
||||||
|
ref: PropTypes.func,
|
||||||
|
placeholder: PropTypes.string,
|
||||||
|
onKeyUp: PropTypes.func,
|
||||||
|
onFocus: PropTypes.func,
|
||||||
|
onBlur: PropTypes.func
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NewMessage
|
|
@ -2,28 +2,28 @@ import React, { PropTypes, Component } from 'react'
|
||||||
|
|
||||||
class Participant extends Component {
|
class Participant extends Component {
|
||||||
render() {
|
render() {
|
||||||
const { conversationLive, mapperIsLive, isParticipating, isPending, id, self, image, username, selfName, color } = this.props
|
const { conversationLive, mapperIsLive, isParticipating, isPending, id, self, image, username, color } = this.props
|
||||||
return (
|
return (
|
||||||
<div className={`participant participant-${id} ${self ? 'is-self' : ''}`}>
|
<div className={`participant participant-${id} ${self ? 'is-self' : ''}`}>
|
||||||
<div className="chat-participant-image">
|
<div className="chat-participant-image">
|
||||||
<img src={image} style={{ border: `2px solid ${color}`}} />
|
<img src={image} style={{ border: `2px solid ${color}` }} />
|
||||||
</div>
|
</div>
|
||||||
<div className="chat-participant-name">
|
<div className="chat-participant-name">
|
||||||
{username} {self ? '(me)' : ''}
|
{username} {self ? '(me)' : ''}
|
||||||
</div>
|
</div>
|
||||||
{!self && !conversationLive && <button
|
{!self && !conversationLive && <button
|
||||||
className={`button chat-participant-invite-call ${isPending ? 'pending' : ''}`}
|
className={`button chat-participant-invite-call ${isPending ? 'pending' : ''}`}
|
||||||
onClick={() => !isPending && this.props.inviteACall(id)} // Realtime.inviteACall(id)
|
onClick={() => !isPending && this.props.inviteACall(id)} // Realtime.inviteACall(id)
|
||||||
/>}
|
/>}
|
||||||
{!self && mapperIsLive && !isParticipating && <button
|
{!self && mapperIsLive && !isParticipating && <button
|
||||||
className={`button chat-participant-invite-join ${isPending ? 'pending' : ''}`}
|
className={`button chat-participant-invite-join ${isPending ? 'pending' : ''}`}
|
||||||
onClick={() => !isPending && this.props.inviteToJoin(id)} // Realtime.inviteToJoin(id)
|
onClick={() => !isPending && this.props.inviteToJoin(id)} // Realtime.inviteToJoin(id)
|
||||||
/>}
|
/>}
|
||||||
{isParticipating && <span className="chat-participant-participating">
|
{isParticipating && <span className="chat-participant-participating">
|
||||||
<div className="green-dot"></div>
|
<div className="green-dot"></div>
|
||||||
</span>}
|
</span>}
|
||||||
<div className="clearfloat"></div>
|
<div className="clearfloat"></div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@ import React, { PropTypes, Component } from 'react'
|
||||||
import Unread from './Unread'
|
import Unread from './Unread'
|
||||||
import Participant from './Participant'
|
import Participant from './Participant'
|
||||||
import Message from './Message'
|
import Message from './Message'
|
||||||
|
import NewMessage from './NewMessage'
|
||||||
|
import Util from '../../Metamaps/Util'
|
||||||
|
|
||||||
function makeList(messages) {
|
function makeList(messages) {
|
||||||
let currentHeader
|
let currentHeader
|
||||||
|
@ -29,7 +31,7 @@ class MapChat extends Component {
|
||||||
messageText: '',
|
messageText: '',
|
||||||
alertSound: true, // whether to play sounds on arrival of new messages or not
|
alertSound: true, // whether to play sounds on arrival of new messages or not
|
||||||
cursorsShowing: true,
|
cursorsShowing: true,
|
||||||
videosShowing: true
|
videosShowing: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,7 +42,7 @@ class MapChat extends Component {
|
||||||
messageText: '',
|
messageText: '',
|
||||||
alertSound: true, // whether to play sounds on arrival of new messages or not
|
alertSound: true, // whether to play sounds on arrival of new messages or not
|
||||||
cursorsShowing: true,
|
cursorsShowing: true,
|
||||||
videosShowing: true
|
videosShowing: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,12 +96,17 @@ class MapChat extends Component {
|
||||||
handleTextareaKeyUp = e => {
|
handleTextareaKeyUp = e => {
|
||||||
if (e.which === 13) {
|
if (e.which === 13) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
const text = this.state.messageText
|
const text = Util.removeEmoji(this.state.messageText)
|
||||||
this.props.handleInputMessage(text)
|
this.props.handleInputMessage(text)
|
||||||
this.setState({ messageText: '' })
|
this.setState({ messageText: '' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
focusMessageInput = () => {
|
||||||
|
if (!this.messageInput) return
|
||||||
|
this.messageInput.focus()
|
||||||
|
}
|
||||||
|
|
||||||
render = () => {
|
render = () => {
|
||||||
const rightOffset = this.state.open ? '0' : '-300px'
|
const rightOffset = this.state.open ? '0' : '-300px'
|
||||||
const { conversationLive, isParticipating, participants, messages, inviteACall, inviteToJoin } = this.props
|
const { conversationLive, isParticipating, participants, messages, inviteACall, inviteToJoin } = this.props
|
||||||
|
@ -116,12 +123,12 @@ class MapChat extends Component {
|
||||||
<div className="participants">
|
<div className="participants">
|
||||||
{conversationLive && <div className="conversation-live">
|
{conversationLive && <div className="conversation-live">
|
||||||
LIVE
|
LIVE
|
||||||
{isParticipating && <span className="call-action leave" onClick={this.props.leaveCall}>
|
{isParticipating && <span className="call-action leave" onClick={this.props.leaveCall}>
|
||||||
LEAVE
|
LEAVE
|
||||||
</span>}
|
</span>}
|
||||||
{!isParticipating && <span className="call-action join" onClick={this.props.joinCall}>
|
{!isParticipating && <span className="call-action join" onClick={this.props.joinCall}>
|
||||||
JOIN
|
JOIN
|
||||||
</span>}
|
</span>}
|
||||||
</div>}
|
</div>}
|
||||||
{participants.map(participant => <Participant
|
{participants.map(participant => <Participant
|
||||||
key={participant.id}
|
key={participant.id}
|
||||||
|
@ -140,17 +147,20 @@ class MapChat extends Component {
|
||||||
<div className="tooltips">Chat</div>
|
<div className="tooltips">Chat</div>
|
||||||
<Unread count={unreadMessages} />
|
<Unread count={unreadMessages} />
|
||||||
</div>
|
</div>
|
||||||
<div className="chat-messages" ref={div => this.messagesDiv = div}>
|
<div className="chat-messages" ref={div => { this.messagesDiv = div }}>
|
||||||
{makeList(messages)}
|
{makeList(messages)}
|
||||||
</div>
|
</div>
|
||||||
<textarea className="chat-input"
|
<NewMessage messageText={this.state.messageText}
|
||||||
ref={textarea => this.messageInput = textarea}
|
focusMessageInput={this.focusMessageInput}
|
||||||
placeholder="Send a message..."
|
handleChange={this.handleChange('messageText')}
|
||||||
value={this.state.messageText}
|
textAreaProps={{
|
||||||
onChange={this.handleChange('messageText')}
|
className: 'chat-input',
|
||||||
onKeyUp={this.handleTextareaKeyUp}
|
ref: textarea => { this.messageInput = textarea },
|
||||||
onFocus={this.props.inputFocus}
|
placeholder: 'Send a message...',
|
||||||
onBlur={this.props.inputBlur}
|
onKeyUp: this.handleTextareaKeyUp,
|
||||||
|
onFocus: this.props.inputFocus,
|
||||||
|
onBlur: this.props.inputBlur
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -168,7 +178,7 @@ MapChat.propTypes = {
|
||||||
inviteToJoin: PropTypes.func,
|
inviteToJoin: PropTypes.func,
|
||||||
videoToggleClick: PropTypes.func,
|
videoToggleClick: PropTypes.func,
|
||||||
cursorToggleClick: PropTypes.func,
|
cursorToggleClick: PropTypes.func,
|
||||||
soundToggleClick: PropTypes.func,
|
soundToggleClick: PropTypes.func,
|
||||||
participants: PropTypes.arrayOf(PropTypes.shape({
|
participants: PropTypes.arrayOf(PropTypes.shape({
|
||||||
color: PropTypes.string, // css color
|
color: PropTypes.string, // css color
|
||||||
id: PropTypes.number,
|
id: PropTypes.number,
|
||||||
|
|
|
@ -41,7 +41,7 @@ class Header extends Component {
|
||||||
linkClass={activeClass('active')}
|
linkClass={activeClass('active')}
|
||||||
data-router="true"
|
data-router="true"
|
||||||
text="All Maps"
|
text="All Maps"
|
||||||
/>
|
/>
|
||||||
<MapLink show={signedIn && explore}
|
<MapLink show={signedIn && explore}
|
||||||
href="/explore/mine"
|
href="/explore/mine"
|
||||||
linkClass={activeClass('my')}
|
linkClass={activeClass('my')}
|
||||||
|
|
|
@ -112,7 +112,7 @@ class MapCard extends Component {
|
||||||
{ mobile && hasConversation && <div className='mobileHasConversation'><MapperList mappers={ mapperList } /></div> }
|
{ mobile && hasConversation && <div className='mobileHasConversation'><MapperList mappers={ mapperList } /></div> }
|
||||||
{ mobile && d && <div className="desc">{ d }</div> }
|
{ mobile && d && <div className="desc">{ d }</div> }
|
||||||
{ mobile && <div className='mobileMetadata'><Metadata map={ map } /></div> }
|
{ mobile && <div className='mobileMetadata'><Metadata map={ map } /></div> }
|
||||||
<div className='creatorAndPerm'>
|
<div className={`creatorAndPerm ${map.authorizeToEdit(currentUser) ? '' : 'cardHasViewOnly'}`}>
|
||||||
<img className='creatorImage' src={ map.get('user_image') } />
|
<img className='creatorImage' src={ map.get('user_image') } />
|
||||||
<span className='creatorName'>{ map.get('user_name') }</span>
|
<span className='creatorName'>{ map.get('user_name') }</span>
|
||||||
{ !map.authorizeToEdit(currentUser) && <div className='cardViewOnly'>View Only</div> }
|
{ !map.authorizeToEdit(currentUser) && <div className='cardViewOnly'>View Only</div> }
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue