Merge branch 'master' into search_posts_with_images

This commit is contained in:
Neil Lalonde 2017-07-26 10:15:55 -04:00 committed by GitHub
commit ea0e90b2b0
18072 changed files with 121466 additions and 34360 deletions

View File

@ -11,7 +11,6 @@ lib/javascripts/messageformat.js
lib/javascripts/moment.js lib/javascripts/moment.js
lib/javascripts/moment_locale/ lib/javascripts/moment_locale/
lib/highlight_js/ lib/highlight_js/
lib/es6_module_transpiler/support/es6-module-transpiler.js
public/javascripts/ public/javascripts/
spec/phantom_js/smoke_test.js spec/phantom_js/smoke_test.js
vendor/ vendor/

View File

@ -6,28 +6,27 @@
"browser": true, "browser": true,
"builtin": true "builtin": true
}, },
ecmaVersion: 7, "parserOptions": {
"ecmaVersion": 7,
"sourceType": "module"
},
"globals": "globals":
{"Ember":true, {"Ember":true,
"jQuery":true, "jQuery":true,
"$":true, "$":true,
"QUnit":true,
"RSVP":true, "RSVP":true,
"Discourse":true, "Discourse":true,
"Em":true, "Em":true,
"Handlebars":true, "Handlebars":true,
"I18n":true, "I18n":true,
"bootbox":true, "bootbox":true,
"module":true,
"moduleFor":true, "moduleFor":true,
"moduleForComponent":true, "moduleForComponent":true,
"Pretender":true, "Pretender":true,
"sandbox":true, "sandbox":true,
"controllerFor":true, "controllerFor":true,
"test":true, "test":true,
"ok":true,
"not":true,
"expect":true,
"equal":true,
"visit":true, "visit":true,
"andThen":true, "andThen":true,
"click":true, "click":true,
@ -48,12 +47,8 @@
"find":true, "find":true,
"sinon":true, "sinon":true,
"moment":true, "moment":true,
"start":true,
"_":true, "_":true,
"alert":true, "alert":true,
"containsInstance":true,
"deepEqual":true,
"notEqual":true,
"define":true, "define":true,
"require":true, "require":true,
"requirejs":true, "requirejs":true,

View File

@ -1,12 +0,0 @@
skip_missing_workers: true
allow_lossy: false
# PNG
advpng: false
optipng:
level: 2
pngcrush: false
pngout: false
pngquant: false
# JPG
jpegrecompress: false
timeout: 15

View File

@ -6,9 +6,7 @@ env:
- RUBY_GC_MALLOC_LIMIT=50000000 - RUBY_GC_MALLOC_LIMIT=50000000
matrix: matrix:
- "RAILS_MASTER=0 QUNIT_RUN=0" - "RAILS_MASTER=0 QUNIT_RUN=0"
- "RAILS_MASTER=1 QUNIT_RUN=0"
- "RAILS_MASTER=0 QUNIT_RUN=1" - "RAILS_MASTER=0 QUNIT_RUN=1"
- "RAILS_MASTER=1 QUNIT_RUN=1"
addons: addons:
postgresql: 9.5 postgresql: 9.5
@ -20,9 +18,6 @@ addons:
- jhead - jhead
matrix: matrix:
allow_failures:
- env: "RAILS_MASTER=1 QUNIT_RUN=0"
- env: "RAILS_MASTER=1 QUNIT_RUN=1"
fast_finish: true fast_finish: true
rvm: rvm:
@ -36,6 +31,7 @@ sudo: required
dist: trusty dist: trusty
cache: cache:
yarn: true
directories: directories:
- vendor/bundle - vendor/bundle
@ -46,7 +42,7 @@ before_install:
- git clone --depth=1 https://github.com/discourse/discourse-cakeday.git plugins/discourse-cakeday - git clone --depth=1 https://github.com/discourse/discourse-cakeday.git plugins/discourse-cakeday
- git clone --depth=1 https://github.com/discourse/discourse-canned-replies.git plugins/discourse-canned-replies - git clone --depth=1 https://github.com/discourse/discourse-canned-replies.git plugins/discourse-canned-replies
- git clone --depth=1 https://github.com/discourse/discourse-slack-official.git plugins/discourse-slack-official - git clone --depth=1 https://github.com/discourse/discourse-slack-official.git plugins/discourse-slack-official
- npm i -g eslint babel-eslint - yarn global add eslint babel-eslint
- eslint app/assets/javascripts - eslint app/assets/javascripts
- eslint --ext .es6 app/assets/javascripts - eslint --ext .es6 app/assets/javascripts
- eslint --ext .es6 test/javascripts - eslint --ext .es6 test/javascripts
@ -61,4 +57,4 @@ install:
- bash -c "if [ '$RAILS_MASTER' == '0' ]; then bundle install --without development --deployment --retry=3 --jobs=3; fi" - bash -c "if [ '$RAILS_MASTER' == '0' ]; then bundle install --without development --deployment --retry=3 --jobs=3; fi"
script: script:
- bash -c "if [ '$QUNIT_RUN' == '0' ]; then bundle exec rspec && bundle exec rake plugin:spec; else bundle exec rake qunit:test['200000']; fi" - bash -c "if [ '$QUNIT_RUN' == '0' ]; then bundle exec rspec && bundle exec rake plugin:spec; else LOAD_PLUGINS=1 bundle exec rake qunit:test['300000']; fi"

View File

@ -26,12 +26,6 @@ source_file = plugins/poll/config/locales/server.en.yml
source_lang = en source_lang = en
type = YML type = YML
[discourse-org.imgurserverenyml]
file_filter = vendor/gems/discourse_imgur/lib/discourse_imgur/locale/server.<lang>.yml
source_file = vendor/gems/discourse_imgur/lib/discourse_imgur/locale/server.en.yml
source_lang = en
type = YML
[discourse-org.narrativeclientenyml] [discourse-org.narrativeclientenyml]
file_filter = plugins/discourse-narrative-bot/config/locales/client.<lang>.yml file_filter = plugins/discourse-narrative-bot/config/locales/client.<lang>.yml
source_file = plugins/discourse-narrative-bot/config/locales/client.en.yml source_file = plugins/discourse-narrative-bot/config/locales/client.en.yml

13
Gemfile
View File

@ -36,6 +36,7 @@ end
gem 'mail' gem 'mail'
gem 'mime-types', require: 'mime/types/columnar' gem 'mime-types', require: 'mime/types/columnar'
gem 'mini_mime'
gem 'hiredis' gem 'hiredis'
gem 'redis', require: ["redis", "redis/connection/hiredis"] gem 'redis', require: ["redis", "redis/connection/hiredis"]
@ -48,10 +49,9 @@ gem 'onebox'
gem 'http_accept_language', '~>2.0.5', require: false gem 'http_accept_language', '~>2.0.5', require: false
gem 'ember-rails', '0.18.5' gem 'ember-rails', '0.18.5'
gem 'ember-source', '2.10.0' gem 'ember-source'
gem 'ember-handlebars-template', '0.7.5' gem 'ember-handlebars-template', '0.7.5'
gem 'barber' gem 'barber'
gem 'babel-transpiler'
gem 'message_bus' gem 'message_bus'
@ -74,6 +74,10 @@ gem 'discourse_image_optim', require: 'image_optim'
gem 'multi_json' gem 'multi_json'
gem 'mustache' gem 'mustache'
gem 'nokogiri' gem 'nokogiri'
# this may end up deprecating nokogiri
gem 'oga', require: false
gem 'omniauth' gem 'omniauth'
gem 'omniauth-openid' gem 'omniauth-openid'
gem 'openid-redis-store' gem 'openid-redis-store'
@ -94,13 +98,13 @@ gem 'r2', '~> 0.2.5', require: false
gem 'rake' gem 'rake'
gem 'thor', require: false gem 'thor', require: false
gem 'rest-client'
gem 'rinku' gem 'rinku'
gem 'sanitize' gem 'sanitize'
gem 'sidekiq' gem 'sidekiq'
# for sidekiq web # for sidekiq web
gem 'sinatra', require: false gem 'tilt', require: false
gem 'execjs', require: false gem 'execjs', require: false
gem 'mini_racer' gem 'mini_racer'
gem 'highline', require: false gem 'highline', require: false
@ -118,7 +122,6 @@ group :test do
gem 'webmock', require: false gem 'webmock', require: false
gem 'fakeweb', '~> 1.3.0', require: false gem 'fakeweb', '~> 1.3.0', require: false
gem 'minitest', require: false gem 'minitest', require: false
gem 'timecop'
# TODO: Remove once we upgrade to Rails 5. # TODO: Remove once we upgrade to Rails 5.
gem 'test_after_commit' gem 'test_after_commit'
end end

View File

@ -39,20 +39,18 @@ GEM
tzinfo (~> 1.1) tzinfo (~> 1.1)
addressable (2.5.1) addressable (2.5.1)
public_suffix (~> 2.0, >= 2.0.2) public_suffix (~> 2.0, >= 2.0.2)
annotate (2.7.1) annotate (2.7.2)
activerecord (>= 3.2, < 6.0) activerecord (>= 3.2, < 6.0)
rake (>= 10.4, < 12.0) rake (>= 10.4, < 13.0)
ansi (1.5.0)
arel (6.0.4) arel (6.0.4)
ast (2.3.0)
aws-sdk (2.5.3) aws-sdk (2.5.3)
aws-sdk-resources (= 2.5.3) aws-sdk-resources (= 2.5.3)
aws-sdk-core (2.5.3) aws-sdk-core (2.5.3)
jmespath (~> 1.0) jmespath (~> 1.0)
aws-sdk-resources (2.5.3) aws-sdk-resources (2.5.3)
aws-sdk-core (= 2.5.3) aws-sdk-core (= 2.5.3)
babel-source (5.8.34)
babel-transpiler (0.7.0)
babel-source (>= 4.0, < 6)
execjs (~> 2.0)
barber (0.11.2) barber (0.11.2)
ember-source (>= 1.0, < 3) ember-source (>= 1.0, < 3)
execjs (>= 1.2, < 3) execjs (>= 1.2, < 3)
@ -62,21 +60,21 @@ 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)
bootsnap (0.3.0) bootsnap (1.0.0)
msgpack (~> 1.0) msgpack (~> 1.0)
builder (3.2.3) builder (3.2.3)
bullet (5.4.2) bullet (5.5.1)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
uniform_notifier (~> 1.10.0) uniform_notifier (~> 1.10.0)
byebug (9.0.6) byebug (9.0.6)
certified (1.0.0) certified (1.0.0)
coderay (1.1.1) coderay (1.1.1)
concurrent-ruby (1.0.5) concurrent-ruby (1.0.5)
connection_pool (2.2.0) connection_pool (2.2.1)
crack (0.4.3) crack (0.4.3)
safe_yaml (~> 1.0.0) safe_yaml (~> 1.0.0)
crass (1.0.2) crass (1.0.2)
debug_inspector (0.0.2) debug_inspector (0.0.3)
diff-lcs (1.3) diff-lcs (1.3)
discourse-qunit-rails (0.0.9) discourse-qunit-rails (0.0.9)
railties railties
@ -86,8 +84,6 @@ GEM
image_size (~> 1.5) image_size (~> 1.5)
in_threads (~> 1.3) in_threads (~> 1.3)
progress (~> 3.0, >= 3.0.1) progress (~> 3.0, >= 3.0.1)
domain_name (0.5.25)
unf (>= 0.0.5, < 1.0.0)
email_reply_trimmer (0.1.6) email_reply_trimmer (0.1.6)
ember-data-source (2.2.1) ember-data-source (2.2.1)
ember-source (>= 1.8, < 3.0) ember-source (>= 1.8, < 3.0)
@ -101,9 +97,9 @@ GEM
ember-source (>= 1.1.0) ember-source (>= 1.1.0)
jquery-rails (>= 1.0.17) jquery-rails (>= 1.0.17)
railties (>= 3.1) railties (>= 3.1)
ember-source (2.10.0) ember-source (2.13.3)
erubis (2.7.0) erubis (2.7.0)
excon (0.55.0) excon (0.56.0)
execjs (2.7.0) execjs (2.7.0)
exifr (1.2.5) exifr (1.2.5)
fabrication (2.9.8) fabrication (2.9.8)
@ -118,32 +114,30 @@ GEM
fastimage (2.1.0) fastimage (2.1.0)
ffi (1.9.18) ffi (1.9.18)
flamegraph (0.9.5) flamegraph (0.9.5)
foreman (0.82.0) foreman (0.84.0)
thor (~> 0.19.1) thor (~> 0.19.1)
fspath (3.1.0) fspath (3.1.0)
gc_tracer (1.5.1) gc_tracer (1.5.1)
globalid (0.3.7) globalid (0.4.0)
activesupport (>= 4.1.0) activesupport (>= 4.2.0)
guess_html_encoding (0.0.11) guess_html_encoding (0.0.11)
hashdiff (0.3.4) hashdiff (0.3.4)
hashie (3.5.5) hashie (3.5.5)
highline (1.7.8) highline (1.7.8)
hiredis (0.6.1) hiredis (0.6.1)
htmlentities (4.3.4) htmlentities (4.3.4)
http-cookie (1.0.2)
domain_name (~> 0.5)
http_accept_language (2.0.5) http_accept_language (2.0.5)
i18n (0.8.1) i18n (0.8.4)
image_size (1.5.0) image_size (1.5.0)
in_threads (1.4.0) in_threads (1.4.0)
jmespath (1.3.1) jmespath (1.3.1)
jquery-rails (4.2.1) jquery-rails (4.3.1)
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)
jwt (1.5.6) jwt (1.5.6)
kgio (2.11.0) kgio (2.11.0)
libv8 (5.3.332.38.5) libv8 (5.7.492.65.1)
listen (3.1.5) listen (3.1.5)
rb-fsevent (~> 0.9, >= 0.9.4) rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7) rb-inotify (~> 0.9, >= 0.9.7)
@ -152,31 +146,31 @@ GEM
loofah (2.0.3) loofah (2.0.3)
nokogiri (>= 1.5.9) nokogiri (>= 1.5.9)
lru_redux (1.1.0) lru_redux (1.1.0)
mail (2.6.6.rc1) mail (2.6.6)
mime-types (>= 1.16, < 4) mime-types (>= 1.16, < 4)
memory_profiler (0.9.7) memory_profiler (0.9.8)
message_bus (2.0.2) message_bus (2.0.2)
rack (>= 1.1.3) rack (>= 1.1.3)
metaclass (0.0.4) metaclass (0.0.4)
method_source (0.8.2) method_source (0.8.2)
mime-types (2.99.3) mime-types (2.99.3)
mini_portile2 (2.1.0) mini_mime (0.1.3)
mini_racer (0.1.9) mini_portile2 (2.2.0)
libv8 (~> 5.3) mini_racer (0.1.11)
minitest (5.10.1) libv8 (~> 5.7)
mocha (1.1.0) minitest (5.10.2)
mocha (1.2.1)
metaclass (~> 0.0.1) metaclass (~> 0.0.1)
mock_redis (0.15.4) mock_redis (0.17.3)
moneta (1.0.0) moneta (1.0.0)
msgpack (1.1.0) msgpack (1.1.0)
multi_json (1.12.1) multi_json (1.12.1)
multi_xml (0.6.0) multi_xml (0.6.0)
multipart-post (2.0.0) multipart-post (2.0.0)
mustache (1.0.5) mustache (1.0.5)
netrc (0.11.0) nokogiri (1.8.0)
nokogiri (1.7.2) mini_portile2 (~> 2.2.0)
mini_portile2 (~> 2.1.0) nokogumbo (1.4.13)
nokogumbo (1.4.10)
nokogiri nokogiri
oauth (0.5.1) oauth (0.5.1)
oauth2 (1.3.1) oauth2 (1.3.1)
@ -185,7 +179,10 @@ GEM
multi_json (~> 1.3) multi_json (~> 1.3)
multi_xml (~> 0.5) multi_xml (~> 0.5)
rack (>= 1.2, < 3) rack (>= 1.2, < 3)
oj (3.0.5) oga (2.10)
ast
ruby-ll (~> 2.1)
oj (3.1.0)
omniauth (1.6.1) omniauth (1.6.1)
hashie (>= 3.4.6, < 3.6.0) hashie (>= 3.4.6, < 3.6.0)
rack (>= 1.6.2, < 3) rack (>= 1.6.2, < 3)
@ -214,7 +211,7 @@ GEM
omniauth-twitter (1.3.0) omniauth-twitter (1.3.0)
omniauth-oauth (~> 1.1) omniauth-oauth (~> 1.1)
rack rack
onebox (1.8.10) onebox (1.8.16)
fast_blank (>= 1.0.0) fast_blank (>= 1.0.0)
htmlentities (~> 4.3) htmlentities (~> 4.3)
moneta (~> 1.0) moneta (~> 1.0)
@ -225,7 +222,7 @@ GEM
openid-redis-store (0.0.2) openid-redis-store (0.0.2)
redis redis
ruby-openid ruby-openid
pg (0.19.0) pg (0.20.0)
progress (3.3.1) progress (3.3.1)
pry (0.10.4) pry (0.10.4)
coderay (~> 1.1.0) coderay (~> 1.1.0)
@ -236,10 +233,10 @@ GEM
pry-rails (0.3.4) pry-rails (0.3.4)
pry (>= 0.9.10) pry (>= 0.9.10)
public_suffix (2.0.5) public_suffix (2.0.5)
puma (3.6.0) puma (3.9.1)
r2 (0.2.6) r2 (0.2.6)
rack (1.6.8) rack (1.6.8)
rack-mini-profiler (0.10.4) rack-mini-profiler (0.10.5)
rack (>= 1.2.0) rack (>= 1.2.0)
rack-openid (1.3.1) rack-openid (1.3.1)
rack (>= 1.1.0) rack (>= 1.1.0)
@ -275,84 +272,79 @@ GEM
rake (>= 0.8.7) rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0) thor (>= 0.18.1, < 2.0)
raindrops (0.18.0) raindrops (0.18.0)
rake (11.3.0) rake (12.0.0)
rake-compiler (0.9.9) rake-compiler (1.0.4)
rake rake
rb-fsevent (0.9.7) rb-fsevent (0.9.8)
rb-inotify (0.9.7) rb-inotify (0.9.8)
ffi (>= 0.5.0) ffi (>= 0.5.0)
rbtrace (0.4.8) rbtrace (0.4.8)
ffi (>= 1.0.6) ffi (>= 1.0.6)
msgpack (>= 0.4.3) msgpack (>= 0.4.3)
trollop (>= 1.16.2) trollop (>= 1.16.2)
redis (3.3.3) redis (3.3.3)
redis-namespace (1.5.2) redis-namespace (1.5.3)
redis (~> 3.0, >= 3.0.4) redis (~> 3.0, >= 3.0.4)
rest-client (1.8.0) rinku (2.0.2)
http-cookie (>= 1.0.2, < 2.0)
mime-types (>= 1.16, < 3.0)
netrc (~> 0.7)
rinku (2.0.0)
rmmseg-cpp (0.2.9) rmmseg-cpp (0.2.9)
rspec (3.4.0) rspec (3.6.0)
rspec-core (~> 3.4.0) rspec-core (~> 3.6.0)
rspec-expectations (~> 3.4.0) rspec-expectations (~> 3.6.0)
rspec-mocks (~> 3.4.0) rspec-mocks (~> 3.6.0)
rspec-core (3.4.4) rspec-core (3.6.0)
rspec-support (~> 3.4.0) rspec-support (~> 3.6.0)
rspec-expectations (3.4.0) rspec-expectations (3.6.0)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.4.0) rspec-support (~> 3.6.0)
rspec-html-matchers (0.7.0) rspec-html-matchers (0.9.1)
nokogiri (~> 1) nokogiri (~> 1)
rspec (~> 3) rspec (>= 3.0.0.a, < 4)
rspec-mocks (3.4.1) rspec-mocks (3.6.0)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.4.0) rspec-support (~> 3.6.0)
rspec-rails (3.4.2) rspec-rails (3.6.0)
actionpack (>= 3.0, < 4.3) actionpack (>= 3.0)
activesupport (>= 3.0, < 4.3) activesupport (>= 3.0)
railties (>= 3.0, < 4.3) railties (>= 3.0)
rspec-core (~> 3.4.0) rspec-core (~> 3.6.0)
rspec-expectations (~> 3.4.0) rspec-expectations (~> 3.6.0)
rspec-mocks (~> 3.4.0) rspec-mocks (~> 3.6.0)
rspec-support (~> 3.4.0) rspec-support (~> 3.6.0)
rspec-support (3.4.1) rspec-support (3.6.0)
rtlit (0.0.5) rtlit (0.0.5)
ruby-ll (2.1.2)
ansi
ast
ruby-openid (2.7.0) ruby-openid (2.7.0)
ruby-readability (0.7.0) ruby-readability (0.7.0)
guess_html_encoding (>= 0.0.4) guess_html_encoding (>= 0.0.4)
nokogiri (>= 1.6.0) nokogiri (>= 1.6.0)
ruby_dep (1.5.0) ruby_dep (1.5.0)
safe_yaml (1.0.4) safe_yaml (1.0.4)
sanitize (4.4.0) sanitize (4.5.0)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.4.4) nokogiri (>= 1.4.4)
nokogumbo (~> 1.4.1) nokogumbo (~> 1.4.1)
sass (3.4.23) sass (3.4.24)
sassc (1.11.2) sassc (1.11.2)
bundler bundler
ffi (~> 1.9.6) ffi (~> 1.9.6)
sass (>= 3.3.0) sass (>= 3.3.0)
seed-fu (2.3.5) seed-fu (2.3.6)
activerecord (>= 3.1, < 4.3) activerecord (>= 3.1)
activesupport (>= 3.1, < 4.3) activesupport (>= 3.1)
shoulda (3.5.0) shoulda (3.5.0)
shoulda-context (~> 1.0, >= 1.0.1) shoulda-context (~> 1.0, >= 1.0.1)
shoulda-matchers (>= 1.4.1, < 3.0) shoulda-matchers (>= 1.4.1, < 3.0)
shoulda-context (1.2.2) shoulda-context (1.2.2)
shoulda-matchers (2.8.0) shoulda-matchers (2.8.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
sidekiq (4.2.4) sidekiq (5.0.3)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
connection_pool (~> 2.2, >= 2.2.0) connection_pool (~> 2.2, >= 2.2.0)
rack-protection (>= 1.5.0) rack-protection (>= 1.5.0)
redis (~> 3.2, >= 3.2.1) redis (~> 3.3, >= 3.3.3)
simple-rss (1.3.1) simple-rss (1.3.1)
sinatra (1.4.6)
rack (~> 1.4)
rack-protection (~> 1.4)
tilt (>= 1.3, < 3)
slop (3.6.0) slop (3.6.0)
spork (1.0.0rc4) spork (1.0.0rc4)
spork-rails (4.0.0) spork-rails (4.0.0)
@ -370,16 +362,15 @@ GEM
activerecord (>= 3.2) activerecord (>= 3.2)
thor (0.19.4) thor (0.19.4)
thread_safe (0.3.6) thread_safe (0.3.6)
tilt (2.0.5) tilt (2.0.7)
timecop (0.8.1)
trollop (2.1.2) trollop (2.1.2)
tzinfo (1.2.3) tzinfo (1.2.3)
thread_safe (~> 0.1) thread_safe (~> 0.1)
uglifier (3.0.2) uglifier (3.2.0)
execjs (>= 0.3.0, < 3) execjs (>= 0.3.0, < 3)
unf (0.1.4) unf (0.1.4)
unf_ext unf_ext
unf_ext (0.0.7.1) unf_ext (0.0.7.4)
unicorn (5.3.0) unicorn (5.3.0)
kgio (~> 2.6) kgio (~> 2.6)
raindrops (~> 0.7) raindrops (~> 0.7)
@ -396,7 +387,6 @@ DEPENDENCIES
active_model_serializers (~> 0.8.3) active_model_serializers (~> 0.8.3)
annotate annotate
aws-sdk aws-sdk
babel-transpiler
barber barber
better_errors better_errors
binding_of_caller binding_of_caller
@ -409,7 +399,7 @@ DEPENDENCIES
email_reply_trimmer (= 0.1.6) email_reply_trimmer (= 0.1.6)
ember-handlebars-template (= 0.7.5) ember-handlebars-template (= 0.7.5)
ember-rails (= 0.18.5) ember-rails (= 0.18.5)
ember-source (= 2.10.0) ember-source
excon excon
execjs execjs
fabrication (= 2.9.8) fabrication (= 2.9.8)
@ -432,6 +422,7 @@ DEPENDENCIES
memory_profiler memory_profiler
message_bus message_bus
mime-types mime-types
mini_mime
mini_racer mini_racer
minitest minitest
mocha mocha
@ -439,6 +430,7 @@ DEPENDENCIES
multi_json multi_json
mustache mustache
nokogiri nokogiri
oga
oj oj
omniauth omniauth
omniauth-facebook omniauth-facebook
@ -465,7 +457,6 @@ DEPENDENCIES
rbtrace rbtrace
redis redis
redis-namespace redis-namespace
rest-client
rinku rinku
rmmseg-cpp rmmseg-cpp
rspec rspec
@ -479,16 +470,15 @@ DEPENDENCIES
shoulda shoulda
sidekiq sidekiq
simple-rss simple-rss
sinatra
spork-rails spork-rails
stackprof stackprof
test_after_commit test_after_commit
thor thor
timecop tilt
uglifier uglifier
unf unf
unicorn unicorn
webmock webmock
BUNDLED WITH BUNDLED WITH
1.14.6 1.15.1

View File

@ -1,4 +1,5 @@
import { ajax } from 'discourse/lib/ajax'; import { ajax } from 'discourse/lib/ajax';
import AdminUser from 'admin/models/admin-user';
export default Ember.Component.extend({ export default Ember.Component.extend({
classNames: ["ip-lookup"], classNames: ["ip-lookup"],
@ -44,7 +45,6 @@ export default Ember.Component.extend({
self.set("totalOthersWithSameIP", result.total); self.set("totalOthersWithSameIP", result.total);
}); });
const AdminUser = require('admin/models/admin-user').default;
AdminUser.findAll("active", data).then(function (users) { AdminUser.findAll("active", data).then(function (users) {
self.setProperties({ self.setProperties({
other_accounts: users, other_accounts: users,

View File

@ -1,3 +1,5 @@
import Permalink from 'admin/models/permalink';
export default Ember.Component.extend({ export default Ember.Component.extend({
classNames: ['permalink-form'], classNames: ['permalink-form'],
formSubmitted: false, formSubmitted: false,
@ -18,8 +20,6 @@ export default Ember.Component.extend({
actions: { actions: {
submit: function() { submit: function() {
const Permalink = require('admin/models/permalink').default;
if (!this.get('formSubmitted')) { if (!this.get('formSubmitted')) {
const self = this; const self = this;
self.set('formSubmitted', true); self.set('formSubmitted', true);

View File

@ -2,11 +2,13 @@ import EmailPreview from 'admin/models/email-preview';
import { popupAjaxError } from 'discourse/lib/ajax-error'; import { popupAjaxError } from 'discourse/lib/ajax-error';
export default Ember.Controller.extend({ export default Ember.Controller.extend({
username: null,
lastSeen: null,
emailEmpty: Em.computed.empty('email'), emailEmpty: Ember.computed.empty('email'),
sendEmailDisabled: Em.computed.or('emailEmpty', 'sendingEmail'), sendEmailDisabled: Ember.computed.or('emailEmpty', 'sendingEmail'),
showSendEmailForm: Em.computed.notEmpty('model.html_content'), showSendEmailForm: Ember.computed.notEmpty('model.html_content'),
htmlEmpty: Em.computed.empty('model.html_content'), htmlEmpty: Ember.computed.empty('model.html_content'),
actions: { actions: {
refresh() { refresh() {
@ -14,7 +16,14 @@ export default Ember.Controller.extend({
this.set('loading', true); this.set('loading', true);
this.set('sentEmail', false); this.set('sentEmail', false);
EmailPreview.findDigest(this.get('lastSeen'), this.get('username')).then(email => {
let username = this.get('username');
if (!username) {
username = this.currentUser.get('username');
this.set('username', username);
}
EmailPreview.findDigest(username, this.get('lastSeen')).then(email => {
model.setProperties(email.getProperties('html_content', 'text_content')); model.setProperties(email.getProperties('html_content', 'text_content'));
this.set('loading', false); this.set('loading', false);
}); });
@ -28,16 +37,14 @@ export default Ember.Controller.extend({
this.set('sendingEmail', true); this.set('sendingEmail', true);
this.set('sentEmail', false); this.set('sentEmail', false);
const self = this; EmailPreview.sendDigest(this.get('username'), this.get('lastSeen'), this.get('email')).then(result => {
EmailPreview.sendDigest(this.get('lastSeen'), this.get('username'), this.get('email')).then(result => {
if (result.errors) { if (result.errors) {
bootbox.alert(result.errors); bootbox.alert(result.errors);
} else { } else {
self.set('sentEmail', true); this.set('sentEmail', true);
} }
}).catch(popupAjaxError).finally(function() { }).catch(popupAjaxError).finally(() => {
self.set('sendingEmail', false); this.set('sendingEmail', false);
}); });
} }
} }

View File

@ -15,6 +15,15 @@ export default Ember.Controller.extend({
]; ];
}.property(), }.property(),
visibilityLevelOptions: function() {
return [
{ name: I18n.t("groups.visibility_levels.public"), value: 0 },
{ name: I18n.t("groups.visibility_levels.members"), value: 1 },
{ name: I18n.t("groups.visibility_levels.staff"), value: 2 },
{ name: I18n.t("groups.visibility_levels.owners"), value: 3 }
];
}.property(),
trustLevelOptions: function() { trustLevelOptions: function() {
return [ return [
{ name: I18n.t("groups.trust_levels.none"), value: 0 }, { name: I18n.t("groups.trust_levels.none"), value: 0 },
@ -22,14 +31,16 @@ export default Ember.Controller.extend({
]; ];
}.property(), }.property(),
@computed('model.visible', 'model.public') @computed('model.visibility_level', 'model.public')
disableMembershipRequestSetting(visible, publicGroup) { disableMembershipRequestSetting(visibility_level, publicGroup) {
return !visible || publicGroup; visibility_level = parseInt(visibility_level);
return (visibility_level !== 0) || publicGroup;
}, },
@computed('model.visible', 'model.allow_membership_requests') @computed('model.visibility_level', 'model.allow_membership_requests')
disablePublicSetting(visible, allowMembershipRequests) { disablePublicSetting(visibility_level, allowMembershipRequests) {
return !visible || allowMembershipRequests; visibility_level = parseInt(visibility_level);
return (visibility_level !== 0) || allowMembershipRequests;
}, },
actions: { actions: {

View File

@ -1,4 +1,6 @@
import AdminUser from 'admin/models/admin-user';
import { ajax } from 'discourse/lib/ajax'; import { ajax } from 'discourse/lib/ajax';
const ApiKey = Discourse.Model.extend({ const ApiKey = Discourse.Model.extend({
/** /**
@ -36,8 +38,7 @@ ApiKey.reopenClass({
@param {...} var_args the properties to initialize this with @param {...} var_args the properties to initialize this with
@returns {ApiKey} the ApiKey instance @returns {ApiKey} the ApiKey instance
**/ **/
create: function() { create() {
const AdminUser = require('admin/models/admin-user').default;
var result = this._super.apply(this, arguments); var result = this._super.apply(this, arguments);
if (result.user) { if (result.user) {
result.user = AdminUser.create(result.user); result.user = AdminUser.create(result.user);

View File

@ -1,42 +1,24 @@
import { ajax } from 'discourse/lib/ajax'; import { ajax } from 'discourse/lib/ajax';
const EmailPreview = Discourse.Model.extend({}); const EmailPreview = Discourse.Model.extend({});
export function oneWeekAgo() {
return moment().locale('en').subtract(7, 'days').format('YYYY-MM-DD');
}
EmailPreview.reopenClass({ EmailPreview.reopenClass({
findDigest: function(lastSeenAt, username) {
if (Em.isEmpty(lastSeenAt)) {
lastSeenAt = this.oneWeekAgo();
}
if (Em.isEmpty(username)) {
username = Discourse.User.current().username;
}
findDigest(username, lastSeenAt) {
return ajax("/admin/email/preview-digest.json", { return ajax("/admin/email/preview-digest.json", {
data: { last_seen_at: lastSeenAt, username: username } data: { last_seen_at: lastSeenAt || oneWeekAgo(), username }
}).then(function (result) { }).then(result => EmailPreview.create(result));
return EmailPreview.create(result);
});
}, },
sendDigest: function(lastSeenAt, username, email) { sendDigest(username, lastSeenAt, email) {
if (Em.isEmpty(lastSeenAt)) {
lastSeenAt = this.oneWeekAgo();
}
if (Em.isEmpty(username)) {
username = Discourse.User.current().username;
}
return ajax("/admin/email/send-digest.json", { return ajax("/admin/email/send-digest.json", {
data: { last_seen_at: lastSeenAt, username: username, email: email } data: { last_seen_at: lastSeenAt || oneWeekAgo(), username, email }
}); });
}, },
oneWeekAgo() {
const en = moment().locale('en');
return en.subtract(7, 'days').format('YYYY-MM-DD');
}
}); });
export default EmailPreview; export default EmailPreview;

View File

@ -37,7 +37,7 @@ export default RestModel.extend({
}, },
groupFinder(term) { groupFinder(term) {
return Group.findAll({search: term, ignore_automatic: false}); return Group.findAll({ term: term, ignore_automatic: false });
}, },
@computed('wildcard_web_hook', 'web_hook_event_types.[]') @computed('wildcard_web_hook', 'web_hook_event_types.[]')
@ -82,4 +82,3 @@ export default RestModel.extend({
return this.createProperties(); return this.createProperties();
} }
}); });

View File

@ -1,16 +1,17 @@
import EmailPreview from 'admin/models/email-preview'; import { default as EmailPreview, oneWeekAgo } from 'admin/models/email-preview';
export default Discourse.Route.extend({ export default Discourse.Route.extend({
model() { model() {
return EmailPreview.findDigest(); return EmailPreview.findDigest(this.currentUser.get('username'));
}, },
afterModel(model) { afterModel(model) {
const controller = this.controllerFor('adminEmailPreviewDigest'); const controller = this.controllerFor('adminEmailPreviewDigest');
controller.setProperties({ controller.setProperties({
model: model, model,
lastSeen: moment().subtract(7, 'days').format('YYYY-MM-DD'), username: this.currentUser.get('username'),
lastSeen: oneWeekAgo(),
showHtml: true showHtml: true
}); });
} }

View File

@ -4,7 +4,7 @@ export default Discourse.Route.extend({
model(params) { model(params) {
if (params.name === 'new') { if (params.name === 'new') {
return Group.create({ automatic: false, visible: true }); return Group.create({ automatic: false, visibility_level: 0 });
} }
const group = this.modelFor('adminGroupsType').findBy('name', params.name); const group = this.modelFor('adminGroupsType').findBy('name', params.name);

View File

@ -1,16 +1,9 @@
/** import Report from 'admin/models/report';
Handles routes for admin reports
@class AdminReportsRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
export default Discourse.Route.extend({ export default Discourse.Route.extend({
queryParams: { mode: {}, "start_date": {}, "end_date": {}, "category_id": {}, "group_id": {} }, queryParams: { mode: {}, "start_date": {}, "end_date": {}, "category_id": {}, "group_id": {} },
model: function(params) { model(params) {
const Report = require('admin/models/report').default;
return Report.find(params.type, params['start_date'], params['end_date'], params['category_id'], params['group_id']); return Report.find(params.type, params['start_date'], params['end_date'], params['category_id'], params['group_id']);
}, },

View File

@ -1,3 +1,8 @@
{{text-field value=value classNames="input-setting-string"}} {{#if setting.textarea}}
{{textarea value=value classNames="input-setting-textarea"}}
{{else}}
{{text-field value=value classNames="input-setting-string"}}
{{/if}}
{{setting-validation-message message=validationMessage}} {{setting-validation-message message=validationMessage}}
<div class='desc'>{{{unbound setting.description}}}</div> <div class='desc'>{{{unbound setting.description}}}</div>

View File

@ -1,11 +1,11 @@
<p>{{i18n 'admin.email.preview_digest_desc'}}</p> <p>{{i18n 'admin.email.preview_digest_desc'}}</p>
<div class='admin-controls'> <div class='admin-controls email-preview'>
<div class='span7 controls'> <div class='span7 controls'>
<label for='last-seen'>{{i18n 'admin.email.last_seen_user'}}</label> <label for='last-seen'>{{i18n 'admin.email.last_seen_user'}}</label>
{{date-picker-past value=lastSeen id="last-seen"}} {{date-picker-past value=lastSeen id="last-seen"}}
<label>{{i18n 'admin.email.user'}}:</label> <label>{{i18n 'admin.email.user'}}:</label>
{{user-selector single="true" usernames=username}} {{user-selector single="true" usernames=username canReceiveUpdates="true"}}
<button class='btn' {{action "refresh"}}>{{i18n 'admin.email.refresh'}}</button> <button class='btn' {{action "refresh"}}>{{i18n 'admin.email.refresh'}}</button>
<div class="toggle"> <div class="toggle">
<label>{{i18n 'admin.email.format'}}</label> <label>{{i18n 'admin.email.format'}}</label>

View File

@ -43,10 +43,8 @@
{{/if}} {{/if}}
<div> <div>
<label> <label for="visiblity">{{i18n 'groups.visibility_levels.title'}}</label>
{{input type="checkbox" checked=model.visible}} {{combo-box name="alias" valueAttribute="value" value=model.visibility_level content=visibilityLevelOptions}}
{{i18n 'groups.visible'}}
</label>
</div> </div>
{{#unless model.automatic}} {{#unless model.automatic}}

View File

@ -27,15 +27,21 @@
//= require ./discourse/lib/eyeline //= require ./discourse/lib/eyeline
//= require ./discourse/lib/show-modal //= require ./discourse/lib/show-modal
//= require ./discourse/mixins/scrolling //= require ./discourse/mixins/scrolling
//= require ./discourse/lib/ajax-error
//= require ./discourse/models/model //= require ./discourse/models/model
//= require ./discourse/models/rest //= require ./discourse/models/rest
//= require ./discourse/models/result-set
//= require ./discourse/models/store
//= require ./discourse/models/action-summary
//= require ./discourse/models/topic
//= require ./discourse/models/draft
//= require ./discourse/models/composer
//= require ./discourse/models/badge-grouping //= require ./discourse/models/badge-grouping
//= require ./discourse/models/badge //= require ./discourse/models/badge
//= require ./discourse/models/permission-type //= require ./discourse/models/permission-type
//= require ./discourse/models/user-action-group //= require ./discourse/models/user-action-group
//= require ./discourse/models/category //= require ./discourse/models/category
//= require ./discourse/models/input-validation //= require ./discourse/models/input-validation
//= require ./discourse/lib/ajax-error
//= require ./discourse/lib/search //= require ./discourse/lib/search
//= require ./discourse/lib/user-search //= require ./discourse/lib/user-search
//= require ./discourse/lib/export-csv //= require ./discourse/lib/export-csv
@ -44,10 +50,7 @@
//= require ./discourse/lib/debounce //= require ./discourse/lib/debounce
//= require ./discourse/lib/safari-hacks //= require ./discourse/lib/safari-hacks
//= require_tree ./discourse/adapters //= require_tree ./discourse/adapters
//= require ./discourse/models/result-set
//= require ./discourse/models/store
//= require ./discourse/models/post-action-type //= require ./discourse/models/post-action-type
//= require ./discourse/models/action-summary
//= require ./discourse/models/post //= require ./discourse/models/post
//= require ./discourse/lib/posts-with-placeholders //= require ./discourse/lib/posts-with-placeholders
//= require ./discourse/models/post-stream //= require ./discourse/models/post-stream
@ -66,8 +69,6 @@
//= require ./discourse/components/notifications-button //= require ./discourse/components/notifications-button
//= require ./discourse/lib/link-mentions //= require ./discourse/lib/link-mentions
//= require ./discourse/components/site-header //= require ./discourse/components/site-header
//= require ./discourse/lib/emoji/groups
//= require ./discourse/lib/emoji/toolbar
//= require ./discourse/components/d-editor //= require ./discourse/components/d-editor
//= require ./discourse/lib/screen-track //= require ./discourse/lib/screen-track
//= require ./discourse/routes/discourse //= require ./discourse/routes/discourse

View File

@ -1,7 +1,4 @@
// ensure Discourse is added as a global
(function() { (function() {
var Discourse = require('discourse').default; window.Discourse = requirejs('discourse').default;
Discourse.dialect_deprecated = true;
window.Discourse = Discourse;
})(); })();

View File

@ -15,7 +15,11 @@ export function getRegister(obj) {
const register = { const register = {
lookup: (...args) => owner.lookup(...args), lookup: (...args) => owner.lookup(...args),
lookupFactory: (...args) => { lookupFactory: (...args) => {
return owner.lookupFactory ? owner.lookupFactory(...args) : owner._lookupFactory(...args); if (owner.factoryFor) {
return owner.factoryFor(...args);
} else if (owner._lookupFactory) {
return owner._lookupFactory(...args);
}
}, },
deprecateContainer(target) { deprecateContainer(target) {

View File

@ -38,7 +38,7 @@ export function buildResolver(baseName) {
resolveRouter(parsedName) { resolveRouter(parsedName) {
const routerPath = `${baseName}/router`; const routerPath = `${baseName}/router`;
if (requirejs.entries[routerPath]) { if (requirejs.entries[routerPath]) {
const module = require(routerPath, null, null, true); const module = requirejs(routerPath, null, null, true);
return module.default; return module.default;
} }
return this._super(parsedName); return this._super(parsedName);
@ -79,7 +79,7 @@ export function buildResolver(baseName) {
var module; var module;
if (moduleName) { if (moduleName) {
module = require(moduleName, null, null, true /* force sync */); module = requirejs(moduleName, null, null, true /* force sync */);
if (module && module['default']) { module = module['default']; } if (module && module['default']) { module = module['default']; }
} }
return module; return module;

View File

@ -1,4 +1,4 @@
var define, require, requirejs; var define, requirejs;
(function() { (function() {
@ -54,7 +54,7 @@ var define, require, requirejs;
var name = this.name; var name = this.name;
return this._require || (this._require = function(dep) { return this._require || (this._require = function(dep) {
return require(resolve(dep, name)); return requirejs(resolve(dep, name));
}); });
}; };
@ -127,7 +127,7 @@ var define, require, requirejs;
if (!mod) { if (!mod) {
throw new Error('Could not find module `' + name + '` imported from `' + origin + '`'); throw new Error('Could not find module `' + name + '` imported from `' + origin + '`');
} }
return require(name); return requirejs(name);
} }
function missingModule(name) { function missingModule(name) {

View File

@ -102,7 +102,7 @@ const Discourse = Ember.Application.extend({
Object.keys(requirejs._eak_seen).forEach(function(key) { Object.keys(requirejs._eak_seen).forEach(function(key) {
if (/\/pre\-initializers\//.test(key)) { if (/\/pre\-initializers\//.test(key)) {
const module = require(key, null, null, true); const module = requirejs(key, null, null, true);
if (!module) { throw new Error(key + ' must export an initializer.'); } if (!module) { throw new Error(key + ' must export an initializer.'); }
const init = module.default; const init = module.default;
@ -117,7 +117,7 @@ const Discourse = Ember.Application.extend({
Object.keys(requirejs._eak_seen).forEach(function(key) { Object.keys(requirejs._eak_seen).forEach(function(key) {
if (/\/initializers\//.test(key)) { if (/\/initializers\//.test(key)) {
const module = require(key, null, null, true); const module = requirejs(key, null, null, true);
if (!module) { throw new Error(key + ' must export an initializer.'); } if (!module) { throw new Error(key + ' must export an initializer.'); }
const init = module.default; const init = module.default;
@ -131,7 +131,7 @@ const Discourse = Ember.Application.extend({
}); });
// Plugins that are registered via `<script>` tags. // Plugins that are registered via `<script>` tags.
const withPluginApi = require('discourse/lib/plugin-api').withPluginApi; const withPluginApi = requirejs('discourse/lib/plugin-api').withPluginApi;
let initCount = 0; let initCount = 0;
_pluginCallbacks.forEach(function(cb) { _pluginCallbacks.forEach(function(cb) {
Discourse.instanceInitializer({ Discourse.instanceInitializer({

View File

@ -5,6 +5,8 @@ import {
SET_BASED_ON_LAST_POST SET_BASED_ON_LAST_POST
} from "discourse/components/auto-update-input-selector"; } from "discourse/components/auto-update-input-selector";
import { PUBLISH_TO_CATEGORY_STATUS_TYPE } from 'discourse/controllers/edit-topic-timer';
export default Ember.Component.extend({ export default Ember.Component.extend({
selection: null, selection: null,
date: null, date: null,
@ -62,10 +64,14 @@ export default Ember.Component.extend({
} }
}, },
@computed("statusType", "input", "isCustom", "date", "time", "willCloseImmediately") @computed("statusType", "input", "isCustom", "date", "time", "willCloseImmediately", "categoryId")
showTopicStatusInfo(statusType, input, isCustom, date, time, willCloseImmediately) { showTopicStatusInfo(statusType, input, isCustom, date, time, willCloseImmediately, categoryId) {
if (!statusType || willCloseImmediately) return false; if (!statusType || willCloseImmediately) return false;
if (statusType === PUBLISH_TO_CATEGORY_STATUS_TYPE && Ember.isEmpty(categoryId)) {
return false;
}
if (isCustom) { if (isCustom) {
return date || time; return date || time;
} else { } else {

View File

@ -39,12 +39,21 @@ export default Ember.Component.extend({
if (!this.site.mobileView) { return; } if (!this.site.mobileView) { return; }
let target = $(e.target); let target = $(e.target);
if (target.closest('.posts-map').length) {
if (target.hasClass('posts-map')) {
const topicId = target.closest('tr').attr('data-topic-id'); const topicId = target.closest('tr').attr('data-topic-id');
if (topicId) { if (topicId) {
if (target.prop('tagName') !== 'A') { if (target.prop('tagName') !== 'A') {
target = target.find('a'); let targetLinks = target.find('a');
if (targetLinks.length) {
target = targetLinks;
} else {
targetLinks = target.closest('a');
if (targetLinks.length) {
target = targetLinks;
} else {
return false;
}
}
} }
const topic = this.get('topics').findBy('id', parseInt(topicId)); const topic = this.get('topics').findBy('id', parseInt(topicId));

View File

@ -42,7 +42,7 @@ export default Combobox.extend({
@computed("rootNone", "rootNoneLabel") @computed("rootNone", "rootNoneLabel")
none(rootNone, rootNoneLabel) { none(rootNone, rootNoneLabel) {
if (Discourse.SiteSettings.allow_uncategorized_topics || this.get('allowUncategorized')) { if (this.siteSettings.allow_uncategorized_topics || this.get('allowUncategorized')) {
if (rootNone) { if (rootNone) {
return rootNoneLabel || "category.none"; return rootNoneLabel || "category.none";
} else { } else {

View File

@ -3,8 +3,9 @@ import Composer from 'discourse/models/composer';
import afterTransition from 'discourse/lib/after-transition'; import afterTransition from 'discourse/lib/after-transition';
import positioningWorkaround from 'discourse/lib/safari-hacks'; import positioningWorkaround from 'discourse/lib/safari-hacks';
import { headerHeight } from 'discourse/components/site-header'; import { headerHeight } from 'discourse/components/site-header';
import KeyEnterEscape from 'discourse/mixins/key-enter-escape';
export default Ember.Component.extend({ export default Ember.Component.extend(KeyEnterEscape, {
elementId: 'reply-control', elementId: 'reply-control',
classNameBindings: ['composer.creatingPrivateMessage:private-message', classNameBindings: ['composer.creatingPrivateMessage:private-message',
@ -65,17 +66,6 @@ export default Ember.Component.extend({
}, 1000); }, 1000);
}, },
keyDown(e) {
if (e.which === 27) {
this.sendAction('cancelled');
return false;
} else if (e.which === 13 && (e.ctrlKey || e.metaKey)) {
// CTRL+ENTER or CMD+ENTER
this.sendAction('save');
return false;
}
},
@observes('composeState') @observes('composeState')
disableFullscreen() { disableFullscreen() {
if (this.get('composeState') !== Composer.OPEN && positioningWorkaround.blur) { if (this.get('composeState') !== Composer.OPEN && positioningWorkaround.blur) {

View File

@ -1,10 +1,11 @@
import userSearch from 'discourse/lib/user-search'; import userSearch from 'discourse/lib/user-search';
import { default as computed, on } from 'ember-addons/ember-computed-decorators'; import { default as computed, on, observes } from 'ember-addons/ember-computed-decorators';
import { linkSeenMentions, fetchUnseenMentions } from 'discourse/lib/link-mentions'; import { linkSeenMentions, fetchUnseenMentions } from 'discourse/lib/link-mentions';
import { linkSeenCategoryHashtags, fetchUnseenCategoryHashtags } from 'discourse/lib/link-category-hashtags'; import { linkSeenCategoryHashtags, fetchUnseenCategoryHashtags } from 'discourse/lib/link-category-hashtags';
import { linkSeenTagHashtags, fetchUnseenTagHashtags } from 'discourse/lib/link-tag-hashtag'; import { linkSeenTagHashtags, fetchUnseenTagHashtags } from 'discourse/lib/link-tag-hashtag';
import Composer from 'discourse/models/composer'; import Composer from 'discourse/models/composer';
import { load } from 'pretty-text/oneboxer'; import { load } from 'pretty-text/oneboxer';
import { applyInlineOneboxes } from 'pretty-text/inline-oneboxer';
import { ajax } from 'discourse/lib/ajax'; import { ajax } from 'discourse/lib/ajax';
import InputValidation from 'discourse/models/input-validation'; import InputValidation from 'discourse/models/input-validation';
import { findRawTemplate } from 'discourse/lib/raw-templates'; import { findRawTemplate } from 'discourse/lib/raw-templates';
@ -30,6 +31,14 @@ export default Ember.Component.extend({
_setupPreview() { _setupPreview() {
const val = (this.site.mobileView ? false : (this.keyValueStore.get('composer.showPreview') || 'true')); const val = (this.site.mobileView ? false : (this.keyValueStore.get('composer.showPreview') || 'true'));
this.set('showPreview', val === 'true'); this.set('showPreview', val === 'true');
this.appEvents.on('composer:show-preview', () => {
this.set('showPreview', true);
});
this.appEvents.on('composer:hide-preview', () => {
this.set('showPreview', false);
});
}, },
@computed('site.mobileView', 'showPreview') @computed('site.mobileView', 'showPreview')
@ -42,9 +51,16 @@ export default Ember.Component.extend({
return showPreview ? I18n.t('composer.hide_preview') : I18n.t('composer.show_preview'); return showPreview ? I18n.t('composer.hide_preview') : I18n.t('composer.show_preview');
}, },
@observes('showPreview')
showPreviewChanged() {
this.keyValueStore.set({ key: 'composer.showPreview', value: this.get('showPreview') });
},
@computed @computed
markdownOptions() { markdownOptions() {
return { return {
previewing: true,
lookupAvatarByPostNumber: (postNumber, topicId) => { lookupAvatarByPostNumber: (postNumber, topicId) => {
const topic = this.get('topic'); const topic = this.get('topic');
if (!topic) { return; } if (!topic) { return; }
@ -158,6 +174,10 @@ export default Ember.Component.extend({
}); });
}, },
_loadInlineOneboxes(inline) {
applyInlineOneboxes(inline, ajax);
},
_loadOneboxes($oneboxes) { _loadOneboxes($oneboxes) {
const post = this.get('composer.post'); const post = this.get('composer.post');
let refresh = false; let refresh = false;
@ -228,6 +248,8 @@ export default Ember.Component.extend({
_bindUploadTarget() { _bindUploadTarget() {
this._unbindUploadTarget(); // in case it's still bound, let's clean it up first this._unbindUploadTarget(); // in case it's still bound, let's clean it up first
this._pasted = false;
const $element = this.$(); const $element = this.$();
const csrf = this.session.get('csrfToken'); const csrf = this.session.get('csrfToken');
const uploadPlaceholder = this.get('uploadPlaceholder'); const uploadPlaceholder = this.get('uploadPlaceholder');
@ -238,10 +260,24 @@ export default Ember.Component.extend({
pasteZone: $element, pasteZone: $element,
}); });
$element.on('fileuploadpaste', () => this._pasted = true);
$element.on('fileuploadsubmit', (e, data) => { $element.on('fileuploadsubmit', (e, data) => {
const isUploading = validateUploadedFiles(data.files); const isPrivateMessage = this.get("composer.privateMessage");
data.formData = { type: "composer" }; data.formData = { type: "composer" };
if (isPrivateMessage) data.formData.for_private_message = true;
if (this._pasted) data.formData.pasted = true;
const opts = {
isPrivateMessage,
allowStaffToUploadAnyFileInPm: this.siteSettings.allow_staff_to_upload_any_file_in_pm,
};
const isUploading = validateUploadedFiles(data.files, opts);
this.setProperties({ uploadProgress: 0, isUploading }); this.setProperties({ uploadProgress: 0, isUploading });
return isUploading; return isUploading;
}); });
@ -250,6 +286,7 @@ export default Ember.Component.extend({
}); });
$element.on("fileuploadsend", (e, data) => { $element.on("fileuploadsend", (e, data) => {
this._pasted = false;
this._validUploads++; this._validUploads++;
this.appEvents.trigger('composer:insert-text', uploadPlaceholder); this.appEvents.trigger('composer:insert-text', uploadPlaceholder);
@ -428,6 +465,8 @@ export default Ember.Component.extend({
@on('willDestroyElement') @on('willDestroyElement')
_composerClosed() { _composerClosed() {
this.appEvents.trigger('composer:will-close'); this.appEvents.trigger('composer:will-close');
this.appEvents.off('composer:show-preview');
this.appEvents.off('composer:hide-preview');
Ember.run.next(() => { Ember.run.next(() => {
$('#main-outlet').css('padding-bottom', 0); $('#main-outlet').css('padding-bottom', 0);
// need to wait a bit for the "slide down" transition of the composer // need to wait a bit for the "slide down" transition of the composer
@ -469,7 +508,6 @@ export default Ember.Component.extend({
togglePreview() { togglePreview() {
this.toggleProperty('showPreview'); this.toggleProperty('showPreview');
this.keyValueStore.set({ key: 'composer.showPreview', value: this.get('showPreview') });
}, },
extraButtons(toolbar) { extraButtons(toolbar) {
@ -541,6 +579,18 @@ export default Ember.Component.extend({
Ember.run.debounce(this, this._loadOneboxes, $oneboxes, 450); Ember.run.debounce(this, this._loadOneboxes, $oneboxes, 450);
} }
let inline = {};
$('a.inline-onebox-loading', $preview).each(function(index, link) {
let $link = $(link);
$link.removeClass('inline-onebox-loading');
let text = $link.text();
inline[text] = inline[text] || [];
inline[text].push($link);
});
if (Object.keys(inline).length > 0) {
Ember.run.debounce(this, this._loadInlineOneboxes, inline, 450);
}
this.trigger('previewRefreshed', $preview); this.trigger('previewRefreshed', $preview);
this.sendAction('afterRefresh', $preview); this.sendAction('afterRefresh', $preview);
}, },

View File

@ -2,6 +2,7 @@ import { default as computed, observes } from 'ember-addons/ember-computed-decor
import InputValidation from 'discourse/models/input-validation'; import InputValidation from 'discourse/models/input-validation';
import { load, lookupCache } from 'pretty-text/oneboxer'; import { load, lookupCache } from 'pretty-text/oneboxer';
import { ajax } from 'discourse/lib/ajax'; import { ajax } from 'discourse/lib/ajax';
import afterTransition from 'discourse/lib/after-transition';
export default Ember.Component.extend({ export default Ember.Component.extend({
classNames: ['title-input'], classNames: ['title-input'],
@ -10,7 +11,11 @@ export default Ember.Component.extend({
didInsertElement() { didInsertElement() {
this._super(); this._super();
if (this.get('focusTarget') === 'title') { if (this.get('focusTarget') === 'title') {
this.$('input').putCursorAtEnd(); const $input = this.$("input");
afterTransition(this.$().closest("#reply-control"), () => {
$input.putCursorAtEnd();
});
} }
}, },

View File

@ -0,0 +1,14 @@
import computed from 'ember-addons/ember-computed-decorators';
export default Ember.Component.extend({
tagName: '',
@computed('composeState')
toggleIcon(composeState) {
if (composeState === "draft" || composeState === "saving") {
return "times";
}
return "chevron-down";
}
});

View File

@ -0,0 +1,15 @@
import { cookAsync } from 'discourse/lib/text';
const CookText = Ember.Component.extend({
tagName: '',
cooked: null,
didReceiveAttrs() {
this._super(...arguments);
cookAsync(this.get('rawText')).then(cooked => this.set('cooked', cooked));
}
});
CookText.reopenClass({ positionalParams: ['rawText'] });
export default CookText;

View File

@ -1,14 +1,13 @@
/*global Mousetrap:true */ /*global Mousetrap:true */
import { default as computed, on, observes } from 'ember-addons/ember-computed-decorators'; import { default as computed, on, observes } from 'ember-addons/ember-computed-decorators';
import { showSelector } from "discourse/lib/emoji/toolbar";
import Category from 'discourse/models/category'; import Category from 'discourse/models/category';
import { categoryHashtagTriggerRule } from 'discourse/lib/category-hashtags'; import { categoryHashtagTriggerRule } from 'discourse/lib/category-hashtags';
import { TAG_HASHTAG_POSTFIX } from 'discourse/lib/tag-hashtags'; import { TAG_HASHTAG_POSTFIX } from 'discourse/lib/tag-hashtags';
import { search as searchCategoryTag } from 'discourse/lib/category-tag-search'; import { search as searchCategoryTag } from 'discourse/lib/category-tag-search';
import { SEPARATOR } from 'discourse/lib/category-hashtags'; import { SEPARATOR } from 'discourse/lib/category-hashtags';
import { cook } from 'discourse/lib/text'; import { cookAsync } from 'discourse/lib/text';
import { translations } from 'pretty-text/emoji/data'; import { translations } from 'pretty-text/emoji/data';
import { emojiSearch } from 'pretty-text/emoji'; import { emojiSearch, isSkinTonableEmoji } from 'pretty-text/emoji';
import { emojiUrlFor } from 'discourse/lib/text'; import { emojiUrlFor } from 'discourse/lib/text';
import { getRegister } from 'discourse-common/lib/get-owner'; import { getRegister } from 'discourse-common/lib/get-owner';
import { findRawTemplate } from 'discourse/lib/raw-templates'; import { findRawTemplate } from 'discourse/lib/raw-templates';
@ -78,7 +77,11 @@ class Toolbar {
group: 'insertions', group: 'insertions',
icon: 'quote-right', icon: 'quote-right',
shortcut: 'Shift+9', shortcut: 'Shift+9',
perform: e => e.applySurround('> ', '', 'code_text') perform: e => e.applyList(
'> ',
'blockquote_text',
{ applyEmptyLines: true, multiline: true }
)
}); });
this.addButton({id: 'code', group: 'insertions', shortcut: 'Shift+C', action: 'formatCode'}); this.addButton({id: 'code', group: 'insertions', shortcut: 'Shift+C', action: 'formatCode'});
@ -138,7 +141,7 @@ class Toolbar {
label: button.label, label: button.label,
icon: button.label ? null : button.icon || button.id, icon: button.label ? null : button.icon || button.id,
action: button.action || 'toolbarButton', action: button.action || 'toolbarButton',
perform: button.perform || Ember.K, perform: button.perform || function() { },
trimLeading: button.trimLeading trimLeading: button.trimLeading
}; };
@ -198,6 +201,7 @@ export default Ember.Component.extend({
linkText: '', linkText: '',
lastSel: null, lastSel: null,
_mouseTrap: null, _mouseTrap: null,
emojiPickerIsActive: false,
@computed('placeholder') @computed('placeholder')
placeholderTranslated(placeholder) { placeholderTranslated(placeholder) {
@ -247,6 +251,7 @@ export default Ember.Component.extend({
}); });
if (this.get('composerEvents')) { if (this.get('composerEvents')) {
this.appEvents.on('composer:insert-block', text => this._addBlock(this._getSelected(), text));
this.appEvents.on('composer:insert-text', text => this._addText(this._getSelected(), text)); this.appEvents.on('composer:insert-text', text => this._addText(this._getSelected(), text));
this.appEvents.on('composer:replace-text', (oldVal, newVal) => this._replaceText(oldVal, newVal)); this.appEvents.on('composer:replace-text', (oldVal, newVal) => this._replaceText(oldVal, newVal));
} }
@ -279,14 +284,14 @@ export default Ember.Component.extend({
const value = this.get('value'); const value = this.get('value');
const markdownOptions = this.get('markdownOptions') || {}; const markdownOptions = this.get('markdownOptions') || {};
markdownOptions.siteSettings = this.siteSettings; cookAsync(value, markdownOptions).then(cooked => {
this.set('preview', cooked);
this.set('preview', cook(value)); Ember.run.scheduleOnce('afterRender', () => {
Ember.run.scheduleOnce('afterRender', () => { if (this._state !== "inDOM") { return; }
if (this._state !== "inDOM") { return; } const $preview = this.$('.d-editor-preview');
const $preview = this.$('.d-editor-preview'); if ($preview.length === 0) return;
if ($preview.length === 0) return; this.sendAction('previewUpdated', $preview);
this.sendAction('previewUpdated', $preview); });
}); });
}, },
@ -327,7 +332,6 @@ export default Ember.Component.extend({
_applyEmojiAutocomplete($editorInput) { _applyEmojiAutocomplete($editorInput) {
if (!this.siteSettings.enable_emoji) { return; } if (!this.siteSettings.enable_emoji) { return; }
const register = this.register;
const self = this; const self = this;
$editorInput.autocomplete({ $editorInput.autocomplete({
@ -337,24 +341,16 @@ export default Ember.Component.extend({
self.set('value', text); self.set('value', text);
}, },
onKeyUp(text, cp) {
return text.substring(0, cp).match(/(:(?!:).?[\w-]*:?(?!:)(?:t\d?)?:?) ?$/g);
},
transformComplete(v) { transformComplete(v) {
if (v.code) { if (v.code) {
return `${v.code}:`; return `${v.code}:`;
} else { } else {
showSelector({ $editorInput.autocomplete({cancel: true});
appendTo: self.$(), self.set('emojiPickerIsActive', true);
register,
onSelect: title => {
// Remove the previously type characters when a new emoji is selected from the selector.
let selected = self._getSelected();
let newPre = selected.pre.replace(/:[^:]+$/, ":");
let numOfRemovedChars = selected.pre.length - newPre.length;
selected.pre = newPre;
selected.start -= numOfRemovedChars;
selected.end -= numOfRemovedChars;
self._addText(selected, `${title}:`);
}
});
return ""; return "";
} }
}, },
@ -372,6 +368,20 @@ export default Ember.Component.extend({
return resolve([translations[full]]); return resolve([translations[full]]);
} }
const match = term.match(/^:?(.*?):t(\d)?$/);
if (match) {
let name = match[1];
let scale = match[2];
if (isSkinTonableEmoji(name)) {
if (scale) {
return resolve([`${name}:t${scale}`]);
} else {
return resolve([2, 3, 4, 5, 6].map(x => `${name}:t${x}`));
}
}
}
const options = emojiSearch(term, {maxResults: 5}); const options = emojiSearch(term, {maxResults: 5});
return resolve(options); return resolve(options);
@ -434,11 +444,15 @@ export default Ember.Component.extend({
}, },
// perform the same operation over many lines of text // perform the same operation over many lines of text
_getMultilineContents(lines, head, hval, hlen, tail, tlen) { _getMultilineContents(lines, head, hval, hlen, tail, tlen, opts) {
let operation = OP.NONE; let operation = OP.NONE;
const applyEmptyLines = opts && opts.applyEmptyLines;
return lines.map(l => { return lines.map(l => {
if (l.length === 0) { return l; } if (!applyEmptyLines && l.length === 0) {
return l;
}
if (operation !== OP.ADDED && if (operation !== OP.ADDED &&
(l.slice(0, hlen) === hval && tlen === 0 || l.slice(-tlen) === tail)) { (l.slice(0, hlen) === hval && tlen === 0 || l.slice(-tlen) === tail)) {
@ -494,8 +508,15 @@ export default Ember.Component.extend({
this.set('value', `${pre.slice(0, -hlen)}${sel.value}${post.slice(tlen)}`); this.set('value', `${pre.slice(0, -hlen)}${sel.value}${post.slice(tlen)}`);
this._selectText(sel.start - hlen, sel.value.length); this._selectText(sel.start - hlen, sel.value.length);
} else { } else {
const contents = this._getMultilineContents(lines, head, hval, hlen, tail, tlen); const contents = this._getMultilineContents(
lines,
head,
hval,
hlen,
tail,
tlen,
opts
);
this.set('value', `${pre}${contents}${post}`); this.set('value', `${pre}${contents}${post}`);
if (lines.length === 1 && tlen > 0) { if (lines.length === 1 && tlen > 0) {
this._selectText(sel.start + hlen, sel.value.length); this._selectText(sel.start + hlen, sel.value.length);
@ -506,9 +527,9 @@ export default Ember.Component.extend({
} }
}, },
_applyList(sel, head, exampleKey) { _applyList(sel, head, exampleKey, opts) {
if (sel.value.indexOf("\n") !== -1) { if (sel.value.indexOf("\n") !== -1) {
this._applySurround(sel, head, '', exampleKey); this._applySurround(sel, head, '', exampleKey, opts);
} else { } else {
const [hval, hlen] = getHead(head); const [hval, hlen] = getHead(head);
@ -553,6 +574,36 @@ export default Ember.Component.extend({
this._selectText(newSelection.start, newSelection.end - newSelection.start); this._selectText(newSelection.start, newSelection.end - newSelection.start);
}, },
_addBlock(sel, text) {
text = (text || '').trim();
if (text.length === 0) {
return;
}
let pre = sel.pre;
let post = sel.value + sel.post;
if (pre.length > 0) {
pre = pre.replace(/\n*$/, "\n\n");
}
if (post.length > 0) {
post = post.replace(/^\n*/, "\n\n");
}
const value = pre + text + post;
const $textarea = this.$('textarea.d-editor-input');
this.set('value', value);
$textarea.val(value);
$textarea.prop("selectionStart", (pre+text).length + 2);
$textarea.prop("selectionEnd", (pre+text).length + 2);
Ember.run.scheduleOnce("afterRender", () => $textarea.focus());
},
_addText(sel, text) { _addText(sel, text) {
const $textarea = this.$('textarea.d-editor-input'); const $textarea = this.$('textarea.d-editor-input');
const insert = `${sel.pre}${text}`; const insert = `${sel.pre}${text}`;
@ -565,13 +616,28 @@ export default Ember.Component.extend({
}, },
actions: { actions: {
emojiSelected(code) {
let selected = this._getSelected();
const captures = selected.pre.match(/\B:(\w*)$/);
if(_.isEmpty(captures)) {
this._addText(selected, `:${code}:`);
} else {
let numOfRemovedChars = selected.pre.length - captures[1].length;
selected.pre = selected.pre.slice(0, selected.pre.length - captures[1].length);
selected.start -= numOfRemovedChars;
selected.end -= numOfRemovedChars;
this._addText(selected, `${code}:`);
}
},
toolbarButton(button) { toolbarButton(button) {
const selected = this._getSelected(button.trimLeading); const selected = this._getSelected(button.trimLeading);
const toolbarEvent = { const toolbarEvent = {
selected, selected,
selectText: (from, length) => this._selectText(from, length), selectText: (from, length) => this._selectText(from, length),
applySurround: (head, tail, exampleKey, opts) => this._applySurround(selected, head, tail, exampleKey, opts), applySurround: (head, tail, exampleKey, opts) => this._applySurround(selected, head, tail, exampleKey, opts),
applyList: (head, exampleKey) => this._applyList(selected, head, exampleKey), applyList: (head, exampleKey, opts) => this._applyList(selected, head, exampleKey, opts),
addText: text => this._addText(selected, text), addText: text => this._addText(selected, text),
replaceText: text => this._addText({pre: '', post: ''}, text), replaceText: text => this._addText({pre: '', post: ''}, text),
getText: () => this.get('value'), getText: () => this.get('value'),
@ -643,11 +709,7 @@ export default Ember.Component.extend({
}, },
emoji() { emoji() {
showSelector({ this.set('emojiPickerIsActive', !this.get('emojiPickerIsActive'));
appendTo: this.$(),
register: this.register,
onSelect: title => this._addText(this._getSelected(), `:${title}:`)
});
} }
} }
}); });

View File

@ -2,7 +2,7 @@
import loadScript from "discourse/lib/load-script"; import loadScript from "discourse/lib/load-script";
import { default as computed, on } from "ember-addons/ember-computed-decorators"; import { default as computed, on } from "ember-addons/ember-computed-decorators";
export default Em.Component.extend({ export default Ember.Component.extend({
classNames: ["date-picker-wrapper"], classNames: ["date-picker-wrapper"],
_picker: null, _picker: null,

View File

@ -0,0 +1,543 @@
import { observes } from "ember-addons/ember-computed-decorators";
import { findRawTemplate } from "discourse/lib/raw-templates";
import { emojiUrlFor } from "discourse/lib/text";
import KeyValueStore from "discourse/lib/key-value-store";
import { emojis } from "pretty-text/emoji/data";
import { extendedEmojiList, isSkinTonableEmoji } from "pretty-text/emoji";
const keyValueStore = new KeyValueStore("discourse_emojis_");
const EMOJI_USAGE = "emojiUsage";
const EMOJI_SELECTED_DIVERSITY = "emojiSelectedDiversity";
const PER_ROW = 11;
const customEmojis = _.map(_.keys(extendedEmojiList()), code => {
return { code, src: emojiUrlFor(code) };
});
export function resetCache() {
keyValueStore.setObject({ key: EMOJI_USAGE, value: [] });
keyValueStore.setObject({ key: EMOJI_SELECTED_DIVERSITY, value: 1 });
}
let $picker, $filter, $results, $list, scrollPosition, $visibleSections, _checkTimeout;
export default Ember.Component.extend({
willDestroyElement() {
this._super();
this._unbindEvents();
this.appEvents.off("emoji-picker:close");
},
didInsertElement() {
this._super();
this.appEvents.on("emoji-picker:close", () => this.set("active", false));
$picker = this.$(".emoji-picker");
if (!keyValueStore.getObject(EMOJI_USAGE)) {
keyValueStore.setObject({ key: EMOJI_USAGE, value: [] });
} else if(_.isPlainObject(keyValueStore.getObject(EMOJI_USAGE))) {
// handle legacy format
keyValueStore.setObject({ key: EMOJI_USAGE, value: _.keys(keyValueStore.getObject(EMOJI_USAGE)) });
}
scrollPosition = 0;
},
didUpdateAttrs() {
this._super();
if (this.get("active")) {
this.show();
} else {
this.close();
}
},
@observes("filter")
filterChanged() {
$filter.find(".clear-filter").toggle(!_.isEmpty(this.get("filter")));
Ember.run.debounce(this, this._filterEmojisList, 250);
},
@observes("selectedDiversity")
selectedDiversityChanged() {
keyValueStore.setObject({key: EMOJI_SELECTED_DIVERSITY, value: this.get("selectedDiversity")});
$.each($list.find(".emoji[data-loaded='1'].diversity"), (_, button) => {
$(button).css("background-image", "").removeAttr("data-loaded");
});
if(this.get("filter") !== "") {
$.each($results.find(".emoji.diversity"), (_, button) => this._setButtonBackground(button, true) );
}
this._updateSelectedDiversity();
},
@observes("recentEmojis")
recentEmojisChanged() {
const previousScrollTop = scrollPosition;
const $recentSection = $list.find(".section[data-section='recent']");
const $recentSectionGroup = $recentSection.find(".section-group");
const $recentCategory = $picker.find(".category-icon button[data-section='recent']").parent();
let persistScrollPosition = !$recentCategory.is(":visible") ? true : false;
// we set height to 0 to avoid it being taken into account for scroll position
if(_.isEmpty(this.get("recentEmojis"))) {
$recentCategory.hide();
$recentSection.css("height", 0).hide();
} else {
$recentCategory.show();
$recentSection.css("height", "auto").show();
}
const recentEmojis = _.map(this.get("recentEmojis"), code => {
return { code, src: emojiUrlFor(code) };
});
const template = findRawTemplate("emoji-picker-recent")({recentEmojis});
$recentSectionGroup.html(template);
if(persistScrollPosition) {
$list.scrollTop(previousScrollTop + $recentSection.outerHeight());
}
this._bindHover($recentSectionGroup);
},
close() {
$picker
.css({width: "", left: "", bottom: "", display: "none"})
.empty();
this.$().find(".emoji-picker-modal").remove();
this._unbindEvents();
clearTimeout(_checkTimeout);
},
show() {
const template = findRawTemplate("emoji-picker")({customEmojis});
$picker.html(template);
this.$().append("<div class='emoji-picker-modal'></div>");
$filter = $picker.find(".filter");
$results = $picker.find(".results");
$list = $picker.find(".list");
this.set("selectedDiversity", keyValueStore.getObject(EMOJI_SELECTED_DIVERSITY) || 1);
this.set("recentEmojis", keyValueStore.getObject(EMOJI_USAGE) || []);
this._bindEvents();
Ember.run.scheduleOnce("afterRender", this, function() {
this._sectionLoadingCheck();
this._loadCategoriesEmojis();
this._positionPicker();
this._scrollTo();
this._updateSelectedDiversity();
});
},
_updateSelectedDiversity() {
const $diversityPicker = $picker.find(".diversity-picker");
$diversityPicker.find(".diversity-scale").removeClass("selected");
$diversityPicker
.find(`.diversity-scale[data-level="${this.get("selectedDiversity")}"]`)
.addClass("selected");
},
_sectionLoadingCheck() {
_checkTimeout = setTimeout(() => { this._sectionLoadingCheck(); }, 500);
Ember.run.throttle(this, this._checkVisibleSection, 100);
},
_loadCategoriesEmojis() {
$.each($picker.find(".categories-column button.emoji"), (_, button) => {
const $button = $(button);
const code = this._codeWithDiversity($button.data("tabicon"), false);
$button.css("background-image", `url("${emojiUrlFor(code)}")`);
});
},
_bindEvents() {
this._bindDiversityClick();
this._bindSectionsScroll();
this._bindEmojiClick($list.find(".section-group"));
this._bindClearRecentEmojisGroup();
this._bindResizing();
this._bindCategoryClick();
this._bindModalClick();
this._bindFilterInput();
if(!this.site.isMobileDevice) {
this._bindHover();
}
},
_bindModalClick() {
this.$(".emoji-picker-modal")
.on("click", () => this.set("active", false));
this.$(document).on("click.emoji-picker", (event) => {
const onPicker = $(event.target).parents(".emoji-picker").length === 1;
const onGrippie = event.target.className.indexOf("grippie") > -1;
if(!onPicker && !onGrippie) {
this.set("active", false);
return false;
}
});
},
_unbindEvents() {
this.$(window).off("resize");
this.$(".emoji-picker-modal").off("click");
Ember.$("#reply-control").off("div-resizing");
this.$(document).off("click.emoji-picker");
},
_filterEmojisList() {
if (this.get("filter") === "") {
$filter.find("input[name='filter']").val("");
$results.empty().hide();
$list.show();
} else {
const lowerCaseFilter = this.get("filter").toLowerCase();
const filterableEmojis = emojis.concat(_.keys(extendedEmojiList()));
const filteredCodes = _.filter(filterableEmojis, code => {
return code.indexOf(lowerCaseFilter) > -1;
}).slice(0, 30);
$results.empty().html(
_.map(filteredCodes, (code) => {
const hasDiversity = isSkinTonableEmoji(code);
const diversity = hasDiversity ? "diversity" : "";
const scaledCode = this._codeWithDiversity(code, hasDiversity);
return `<button style="background-image: url('${emojiUrlFor(scaledCode)}')" type="button" class="emoji ${diversity}" tabindex="-1" title="${code}"></button>`;
})
).show();
this._bindHover($results);
this._bindEmojiClick($results);
$list.hide();
}
},
_bindFilterInput() {
const $input = $filter.find("input");
$input.on("input", (event) => {
this.set("filter", event.currentTarget.value);
});
$filter.find(".clear-filter").on("click", () => {
$input.val("").focus();
this.set("filter", "");
return false;
});
},
_bindCategoryClick() {
$picker.find(".category-icon").on("click", "button.emoji", (event) => {
this.set("filter", "");
$results.empty();
$list.show();
const section = $(event.currentTarget).data("section");
const $section = $list.find(`.section[data-section="${section}"]`);
const scrollTop = $list.scrollTop() + ($section.offset().top - $list.offset().top);
this._scrollTo(scrollTop);
return false;
});
},
_bindHover($hoverables) {
const replaceInfoContent = (html) => $picker.find(".footer .info").html(html || "");
($hoverables || $list.find(".section-group")).on({
mouseover: (event) => {
const code = this._codeForEmojiButton($(event.currentTarget));
const html = `<img src="${emojiUrlFor(code)}" class="emoji"> <span>:${code}:<span>`;
replaceInfoContent(html);
},
mouseleave: () => replaceInfoContent()
}, "button.emoji");
},
_bindResizing() {
this.$(window).on("resize", () => {
Ember.run.throttle(this, this._positionPicker, 16);
});
Ember.$("#reply-control").on("div-resizing", () => {
Ember.run.throttle(this, this._positionPicker, 16);
});
},
_bindClearRecentEmojisGroup() {
const $recent = $picker.find(".section[data-section='recent'] .clear-recent");
$recent.on("click", () => {
keyValueStore.setObject({ key: EMOJI_USAGE, value: [] });
this.set("recentEmojis", []);
this._scrollTo(0);
return false;
});
},
_bindEmojiClick($emojisContainer) {
const handler = (event) => {
const code = this._codeForEmojiButton($(event.currentTarget));
if($(event.currentTarget).parents(".section[data-section='recent']").length === 0) {
this._trackEmojiUsage(code);
}
this.sendAction("emojiSelected", code);
if(this.$(".emoji-picker-modal").hasClass("fadeIn")) {
this.set("active", false);
}
return false;
};
if(this.site.isMobileDevice) {
const self = this;
$emojisContainer
.off("touchstart")
.on("touchstart", "button.emoji", (touchStartEvent) => {
const $this = $(touchStartEvent.currentTarget);
$this.on("touchend", (touchEndEvent) => {
handler.bind(self)(touchEndEvent);
$this.off("touchend");
});
$this.on("touchmove", () => $this.off("touchend") );
});
} else {
$emojisContainer.off("click").on("click", "button.emoji", e => handler.bind(this)(e) );
}
},
_bindSectionsScroll() {
$list.on("scroll", () => {
scrollPosition = $list.scrollTop();
Ember.run.throttle(this, this._checkVisibleSection, 150);
});
},
_checkVisibleSection() {
// make sure we stop loading if picker has been removed
if(!$picker) {
return;
}
const $sections = $list.find(".section");
const listHeight = $list.innerHeight();
let $selectedSection;
$visibleSections = _.filter($sections, section => {
const $section = $(section);
const sectionTop = $section.position().top;
return sectionTop + $section.height() > 0 && sectionTop < listHeight;
});
if (!_.isEmpty(this.get("recentEmojis")) && scrollPosition === 0) {
$selectedSection = $(_.first($visibleSections));
} else {
$selectedSection = $(_.last($visibleSections));
}
if($selectedSection) {
$picker.find(".category-icon").removeClass("current");
$picker.find(`.category-icon button[data-section='${$selectedSection.data("section")}']`)
.parent()
.addClass("current");
this._loadVisibleSections();
}
},
_loadVisibleSections() {
if(!$visibleSections) {
return;
}
const listHeight = $list.innerHeight();
$visibleSections.forEach(visibleSection => {
const $unloadedEmojis = $(visibleSection).find("button.emoji[data-loaded!='1']");
$.each($unloadedEmojis, (_, button) => {
const $button = $(button);
const buttonTop = $button.position().top;
const buttonHeight = $button.height();
if(buttonTop + buttonHeight > 0 && buttonTop - buttonHeight < listHeight) {
this._setButtonBackground($button);
}
});
});
},
_bindDiversityClick() {
const $diversityScales = $picker.find(".diversity-picker .diversity-scale");
$diversityScales.on("click", (event) => {
const $selectedDiversity = $(event.currentTarget);
this.set("selectedDiversity", parseInt($selectedDiversity.data("level")));
return false;
});
},
_isReplyControlExpanded() {
const verticalSpace = this.$(window).height() -
Ember.$(".d-header").height() -
Ember.$("#reply-control").height();
return verticalSpace < $picker.height() - 48;
},
_positionPicker(){
if(!this.get("active")) { return; }
let windowWidth = this.$(window).width();
const desktopModalePositioning = options => {
let attributes = {
width: Math.min(windowWidth, 400) - 12,
marginLeft: -(Math.min(windowWidth, 400)/2) + 6,
marginTop: -130,
left: "50%",
bottom: "",
top: "50%",
display: "flex"
};
this.$(".emoji-picker-modal").addClass("fadeIn");
$picker.css(_.merge(attributes, options));
};
const mobilePositioning = options => {
let attributes = {
width: windowWidth - 12,
marginLeft: 5,
marginTop: -130,
left: 0,
bottom: "",
top: "50%",
display: "flex"
};
this.$(".emoji-picker-modal").addClass("fadeIn");
$picker.css(_.merge(attributes, options));
};
const desktopPositioning = options => {
let attributes = {
width: windowWidth < 485 ? windowWidth - 12 : 400,
marginLeft: "",
marginTop: "",
right: "",
left: "",
bottom: 32,
top: "",
display:
"flex"
};
this.$(".emoji-picker-modal").removeClass("fadeIn");
$picker.css(_.merge(attributes, options));
};
if(Ember.testing) {
desktopPositioning();
return;
}
if(this.site.isMobileDevice) {
mobilePositioning();
} else {
if(this._isReplyControlExpanded()) {
let $editorWrapper = Ember.$(".d-editor-preview-wrapper");
if(($editorWrapper.is(":visible") && $editorWrapper.width() < 400) || windowWidth < 485) {
desktopModalePositioning();
} else {
if($editorWrapper.is(":visible")) {
let previewOffset = Ember.$(".d-editor-preview-wrapper").offset();
let replyControlOffset = Ember.$("#reply-control").offset();
let left = previewOffset.left - replyControlOffset.left;
desktopPositioning({left});
} else {
desktopPositioning({
right: (Ember.$("#reply-control").width() - Ember.$(".d-editor-container").width()) / 2
});
}
}
} else {
if(windowWidth < 485) {
desktopModalePositioning();
} else {
let previewInputOffset = Ember.$(".d-editor-input").offset();
let replyControlOffset = Ember.$("#reply-control").offset() || {left: 0};
let left = previewInputOffset.left - replyControlOffset.left;
desktopPositioning({left, bottom: Ember.$("#reply-control").height() - 48});
}
}
}
const infoMaxWidth = $picker.width() -
$picker.find(".categories-column").width() -
$picker.find(".diversity-picker").width() -
32;
$picker.find(".info").css("max-width", infoMaxWidth);
},
_codeWithDiversity(code, diversity) {
if(diversity && this.get("selectedDiversity") !== 1) {
return `${code}:t${this.get("selectedDiversity")}`;
} else {
return code;
}
},
_trackEmojiUsage(code) {
let recent = keyValueStore.getObject(EMOJI_USAGE) || [];
recent = recent.filter(r => r !== code);
recent.unshift(code);
recent.length = Math.min(recent.length, PER_ROW);
keyValueStore.setObject({ key: EMOJI_USAGE, value: recent });
this.set("recentEmojis", recent);
},
_scrollTo(y) {
const yPosition = _.isUndefined(y) ? scrollPosition : y;
$list.scrollTop(yPosition);
// if we dont actually scroll we need to force it
if(yPosition === 0) {
$list.scroll();
}
},
_codeForEmojiButton($button) {
const title = $button.attr("title");
return this._codeWithDiversity(title, $button.hasClass("diversity"));
},
_setButtonBackground(button, diversity) {
const $button = $(button);
const code = this._codeWithDiversity(
$button.attr("title"),
diversity || $button.hasClass("diversity")
);
// force visual reloading if needed
if($button.css("background-image") !== "none") {
$button.css("background-image", "");
}
$button
.attr("data-loaded", 1)
.css("background-image", `url("${emojiUrlFor(code)}")`);
},
});

View File

@ -0,0 +1,19 @@
import { ajax } from 'discourse/lib/ajax';
export default Ember.Component.extend({
tagName: '',
actions: {
expandItem() {
const item = this.get('item');
const topicId = item.get('topic_id');
const postNumber = item.get('post_number');
return ajax(`/posts/by_number/${topicId}/${postNumber}.json`).then(result => {
item.set('truncated', false);
item.set('excerpt', result.cooked);
});
}
}
});

View File

@ -0,0 +1,16 @@
import { default as computed } from 'ember-addons/ember-computed-decorators';
export default Ember.Component.extend({
tagName: 'button',
classNames: ['btn-flat'],
attributeBindings: ['disabled', 'translatedTitle:title'],
@computed("title")
translatedTitle(title) {
if (title) return I18n.t(title);
},
click() {
return this.attrs.action();
}
});

View File

@ -1,8 +1,10 @@
import { default as computed } from 'ember-addons/ember-computed-decorators'; import { default as computed } from 'ember-addons/ember-computed-decorators';
import { popupAjaxError } from 'discourse/lib/ajax-error'; import { popupAjaxError } from 'discourse/lib/ajax-error';
import Group from 'discourse/models/group'; import DiscourseURL from 'discourse/lib/url';
export default Ember.Component.extend({ export default Ember.Component.extend({
loading: false,
@computed("model.public") @computed("model.public")
canJoinGroup(publicGroup) { canJoinGroup(publicGroup) {
return publicGroup; return publicGroup;
@ -17,22 +19,6 @@ export default Ember.Component.extend({
} }
}, },
@computed
disableRequestMembership() {
if (this.currentUser) {
return this.currentUser.trust_level < this.siteSettings.min_trust_to_send_messages;
} else {
return false;
}
},
@computed("disableRequestMembership")
requestMembershipButtonTitle(disableRequestMembership) {
if (disableRequestMembership) {
return "groups.request_membership_pm.disabled";
}
},
_showLoginModal() { _showLoginModal() {
this.sendAction('showLogin'); this.sendAction('showLogin');
$.cookie('destination_url', window.location.href); $.cookie('destination_url', window.location.href);
@ -67,13 +53,12 @@ export default Ember.Component.extend({
requestMembership() { requestMembership() {
if (this.currentUser) { if (this.currentUser) {
const groupName = this.get('model.name'); this.set('loading', true);
Group.loadOwners(groupName).then(result => { this.get('model').requestMembership().then(result => {
const names = result.map(owner => owner.username).join(","); DiscourseURL.routeTo(result.relative_url);
const title = I18n.t('groups.request_membership_pm.title'); }).catch(popupAjaxError).finally(() => {
const body = I18n.t('groups.request_membership_pm.body', { groupName }); this.set('loading', false);
this.sendAction("createNewMessageViaParams", names, title, body);
}); });
} else { } else {
this._showLoginModal(); this._showLoginModal();

View File

@ -1,8 +0,0 @@
export default Ember.Component.extend({
actions: {
// TODO: When on Ember 1.13, use a closure action
loadMore() {
this.sendAction('loadMore');
}
}
});

View File

@ -15,31 +15,27 @@ export default Ember.Component.extend({
@on('didInsertElement') @on('didInsertElement')
_initializeAutocomplete(opts) { _initializeAutocomplete(opts) {
var self = this; let selectedGroups;
var selectedGroups; let groupNames = this.get('groupNames');
var groupNames = this.get('groupNames');
self.$('input').autocomplete({ this.$('input').autocomplete({
allowAny: false, allowAny: false,
items: _.isArray(groupNames) ? groupNames : (Ember.isEmpty(groupNames)) ? [] : [groupNames], items: _.isArray(groupNames) ? groupNames : (Ember.isEmpty(groupNames)) ? [] : [groupNames],
single: this.get('single'), single: this.get('single'),
updateData: (opts && opts.updateData) ? opts.updateData : false, updateData: (opts && opts.updateData) ? opts.updateData : false,
onChangeItems: function(items){ onChangeItems: items => {
selectedGroups = items; selectedGroups = items;
self.set("groupNames", items.join(",")); this.set("groupNames", items.join(","));
}, },
transformComplete: function(g) { transformComplete: g => {
return g.name; return g.name;
}, },
dataSource: function(term) { dataSource: term => {
return self.get("groupFinder")(term).then(function(groups){ return this.get("groupFinder")(term).then(groups => {
if(!selectedGroups) return groups;
if(!selectedGroups){ return groups.filter(group => {
return groups; return !selectedGroups.any(s => s === group.name);
}
return groups.filter(function(group){
return !selectedGroups.any(function(s){return s === group.name;});
}); });
}); });
}, },

View File

@ -1,13 +1,11 @@
import highlightText from 'discourse/lib/highlight-text';
export default Ember.Component.extend({ export default Ember.Component.extend({
tagName: 'span', tagName: 'span',
_highlightOnInsert: function() { _highlightOnInsert: function() {
const term = this.get('highlight'); const term = this.get('highlight');
const self = this; highlightText(this.$(), term);
if(!_.isEmpty(term)) {
self.$().highlight(term.split(/\s+/), {className: 'search-highlight'});
}
}.observes('highlight').on('didInsertElement') }.observes('highlight').on('didInsertElement')
}); });

View File

@ -1,8 +1,8 @@
import { keyDirty } from 'discourse/widgets/widget';
import { diff, patch } from 'virtual-dom'; import { diff, patch } from 'virtual-dom';
import { WidgetClickHook } from 'discourse/widgets/hooks'; import { WidgetClickHook } from 'discourse/widgets/hooks';
import { renderedKey, queryRegistry } from 'discourse/widgets/widget'; import { queryRegistry } from 'discourse/widgets/widget';
import { getRegister } from 'discourse-common/lib/get-owner'; import { getRegister } from 'discourse-common/lib/get-owner';
import DirtyKeys from 'discourse/lib/dirty-keys';
const _cleanCallbacks = {}; const _cleanCallbacks = {};
export function addWidgetCleanCallback(widgetName, fn) { export function addWidgetCleanCallback(widgetName, fn) {
@ -18,6 +18,7 @@ export default Ember.Component.extend({
_renderCallback: null, _renderCallback: null,
_childEvents: null, _childEvents: null,
_dispatched: null, _dispatched: null,
dirtyKeys: null,
init() { init() {
this._super(); this._super();
@ -34,6 +35,7 @@ export default Ember.Component.extend({
this._childEvents = []; this._childEvents = [];
this._connected = []; this._connected = [];
this._dispatched = []; this._dispatched = [];
this.dirtyKeys = new DirtyKeys(name);
}, },
didInsertElement() { didInsertElement() {
@ -73,7 +75,7 @@ export default Ember.Component.extend({
eventDispatched(eventName, key, refreshArg) { eventDispatched(eventName, key, refreshArg) {
const onRefresh = Ember.String.camelize(eventName.replace(/:/, '-')); const onRefresh = Ember.String.camelize(eventName.replace(/:/, '-'));
keyDirty(key, { onRefresh, refreshArg }); this.dirtyKeys.keyDirty(key, { onRefresh, refreshArg });
this.queueRerender(); this.queueRerender();
}, },
@ -104,7 +106,10 @@ export default Ember.Component.extend({
const t0 = new Date().getTime(); const t0 = new Date().getTime();
const args = this.get('args') || this.buildArgs(); const args = this.get('args') || this.buildArgs();
const opts = { model: this.get('model') }; const opts = {
model: this.get('model'),
dirtyKeys: this.dirtyKeys,
};
const newTree = new this._widgetClass(args, this.register, opts); const newTree = new this._widgetClass(args, this.register, opts);
newTree._rerenderable = this; newTree._rerenderable = this;
@ -122,8 +127,8 @@ export default Ember.Component.extend({
this._renderCallback = null; this._renderCallback = null;
} }
this.afterRender(); this.afterRender();
this.dirtyKeys.renderedKey('*');
Ember.run.scheduleOnce('afterRender', () => renderedKey('*'));
if (this.profileWidget) { if (this.profileWidget) {
console.log(new Date().getTime() - t0); console.log(new Date().getTime() - t0);
} }

View File

@ -1,5 +1,4 @@
import DiscourseURL from 'discourse/lib/url'; import DiscourseURL from 'discourse/lib/url';
import { keyDirty } from 'discourse/widgets/widget';
import MountWidget from 'discourse/components/mount-widget'; import MountWidget from 'discourse/components/mount-widget';
import { cloak, uncloak } from 'discourse/widgets/post-stream'; import { cloak, uncloak } from 'discourse/widgets/post-stream';
import { isWorkaroundActive } from 'discourse/lib/safari-hacks'; import { isWorkaroundActive } from 'discourse/lib/safari-hacks';
@ -245,13 +244,13 @@ export default MountWidget.extend({
this.appEvents.on('post-stream:refresh', args => { this.appEvents.on('post-stream:refresh', args => {
if (args) { if (args) {
if (args.id) { if (args.id) {
keyDirty(`post-${args.id}`); this.dirtyKeys.keyDirty(`post-${args.id}`);
if (args.refreshLikes) { if (args.refreshLikes) {
keyDirty(`post-menu-${args.id}`, { onRefresh: 'refreshLikes' }); this.dirtyKeys.keyDirty(`post-menu-${args.id}`, { onRefresh: 'refreshLikes' });
} }
} else if (args.force) { } else if (args.force) {
keyDirty(`*`); this.dirtyKeys.forceAll();
} }
} }
this.queueRerender(); this.queueRerender();

View File

@ -1,5 +1,7 @@
import { observes } from 'ember-addons/ember-computed-decorators'; import { observes } from 'ember-addons/ember-computed-decorators';
import { escapeExpression } from 'discourse/lib/utilities'; import { escapeExpression } from 'discourse/lib/utilities';
import Group from 'discourse/models/group';
import Badge from 'discourse/models/badge';
const REGEXP_BLOCKS = /(([^" \t\n\x0B\f\r]+)?(("[^"]+")?))/g; const REGEXP_BLOCKS = /(([^" \t\n\x0B\f\r]+)?(("[^"]+")?))/g;
@ -80,7 +82,8 @@ export default Em.Component.extend({
likes: false, likes: false,
private: false, private: false,
seen: false seen: false
} },
all_tags: false
}, },
status: '', status: '',
min_post_count: '', min_post_count: '',
@ -233,13 +236,15 @@ export default Em.Component.extend({
const match = this.filterBlocks(REGEXP_TAGS_PREFIX); const match = this.filterBlocks(REGEXP_TAGS_PREFIX);
const tags = this.get('searchedTerms.tags'); const tags = this.get('searchedTerms.tags');
const contain_all_tags = this.get('searchedTerms.special.all_tags');
if (match.length !== 0) { if (match.length !== 0) {
const existingInput = _.isArray(tags) ? tags.join(',') : tags; const join_char = contain_all_tags ? '+' : ',';
const existingInput = _.isArray(tags) ? tags.join(join_char) : tags;
const userInput = match[0].replace(REGEXP_TAGS_REPLACE, ''); const userInput = match[0].replace(REGEXP_TAGS_REPLACE, '');
if (existingInput !== userInput) { if (existingInput !== userInput) {
this.set('searchedTerms.tags', (userInput.length !== 0) ? userInput.split(',') : []); this.set('searchedTerms.tags', (userInput.length !== 0) ? userInput.split(join_char) : []);
} }
} else if (tags.length !== 0) { } else if (tags.length !== 0) {
this.set('searchedTerms.tags', []); this.set('searchedTerms.tags', []);
@ -368,14 +373,16 @@ export default Em.Component.extend({
} }
}, },
@observes('searchedTerms.tags') @observes('searchedTerms.tags', 'searchedTerms.special.all_tags')
updateSearchTermForTags() { updateSearchTermForTags() {
const match = this.filterBlocks(REGEXP_TAGS_PREFIX); const match = this.filterBlocks(REGEXP_TAGS_PREFIX);
const tagFilter = this.get('searchedTerms.tags'); const tagFilter = this.get('searchedTerms.tags');
let searchTerm = this.get('searchTerm') || ''; let searchTerm = this.get('searchTerm') || '';
const contain_all_tags = this.get('searchedTerms.special.all_tags');
if (tagFilter && tagFilter.length !== 0) { if (tagFilter && tagFilter.length !== 0) {
const tags = tagFilter.join(','); const join_char = contain_all_tags ? '+' : ',';
const tags = tagFilter.join(join_char);
if (match.length !== 0) { if (match.length !== 0) {
searchTerm = searchTerm.replace(match[0], `tags:${tags}`); searchTerm = searchTerm.replace(match[0], `tags:${tags}`);
@ -527,12 +534,10 @@ export default Em.Component.extend({
}, },
groupFinder(term) { groupFinder(term) {
const Group = require('discourse/models/group').default; return Group.findAll({ term: term, ignore_automatic: false });
return Group.findAll({search: term, ignore_automatic: false});
}, },
badgeFinder(term) { badgeFinder(term) {
const Badge = require('discourse/models/badge').default;
return Badge.findAll({search: term}); return Badge.findAll({search: term});
} }
}); });

View File

@ -2,13 +2,7 @@ import { propertyEqual } from 'discourse/lib/computed';
import { actionDescription } from "discourse/components/small-action"; import { actionDescription } from "discourse/components/small-action";
export default Ember.Component.extend({ export default Ember.Component.extend({
classNameBindings: [":item", "item.hidden", "item.deleted", "moderatorAction"], classNameBindings: [":item", "item.hidden", "item.deleted:deleted", "moderatorAction"],
moderatorAction: propertyEqual("item.post_type", "site.post_types.moderator_action"), moderatorAction: propertyEqual("item.post_type", "site.post_types.moderator_action"),
actionDescription: actionDescription("item.action_code", "item.created_at", "item.username"), actionDescription: actionDescription("item.action_code", "item.created_at", "item.username"),
actions: {
removeBookmark(userAction) {
this.sendAction("removeBookmark", userAction);
}
}
}); });

View File

@ -1,10 +1,11 @@
import MountWidget from 'discourse/components/mount-widget'; import MountWidget from 'discourse/components/mount-widget';
export default MountWidget.extend({ export default MountWidget.extend({
classNames: 'topic-admin-menu-button-container',
tagName: 'span', tagName: 'span',
widget: "topic-admin-menu-button", widget: "topic-admin-menu-button",
buildArgs() { buildArgs() {
return this.getProperties('topic', 'fixed', 'openUpwards'); return this.getProperties('topic', 'fixed', 'openUpwards', 'rightSide');
} }
}); });

View File

@ -92,17 +92,18 @@ export default Ember.Component.extend(CleansUp, {
this.appEvents.off('topic-entrance:show'); this.appEvents.off('topic-entrance:show');
}, },
_jumpTo(destination) {
this.cleanUp();
DiscourseURL.routeTo(destination);
},
actions: { actions: {
enterTop() { enterTop() {
const topic = this.get('topic'); this._jumpTo(this.get('topic.url'));
this.appEvents.trigger('header:update-topic', topic);
DiscourseURL.routeTo(topic.get('url'));
}, },
enterBottom() { enterBottom() {
const topic = this.get('topic'); this._jumpTo(this.get('topic.lastPostUrl'));
this.appEvents.trigger('header:update-topic', topic);
DiscourseURL.routeTo(topic.get('lastPostUrl'));
} }
} }
}); });

View File

@ -20,7 +20,7 @@ export default Ember.Component.extend({
@computed('postStream.loaded', 'topic.currentPost', 'postStream.filteredPostsCount') @computed('postStream.loaded', 'topic.currentPost', 'postStream.filteredPostsCount')
hideProgress(loaded, currentPost, filteredPostsCount) { hideProgress(loaded, currentPost, filteredPostsCount) {
return (!loaded) || (!currentPost) || (filteredPostsCount < 2); return (!loaded) || (!currentPost) || (!this.site.mobileView && filteredPostsCount < 2);
}, },
@computed('postStream.filteredPostsCount') @computed('postStream.filteredPostsCount')
@ -52,8 +52,14 @@ export default Ember.Component.extend({
}, },
_topicScrolled(event) { _topicScrolled(event) {
this.set('progressPosition', event.postIndex); if (this.get('docked')) {
this._streamPercentage = event.percent; this.set('progressPosition', this.get('postStream.filteredPostsCount'));
this._streamPercentage = 1.0;
} else {
this.set('progressPosition', event.postIndex);
this._streamPercentage = event.percent;
}
this._updateBar(); this._updateBar();
}, },
@ -92,10 +98,14 @@ export default Ember.Component.extend({
if (!this._totalWidth) { if (!this._totalWidth) {
this._totalWidth = $topicProgress[0].offsetWidth; this._totalWidth = $topicProgress[0].offsetWidth;
} }
// Only show percentage once we have one
if (!this._streamPercentage) { return; }
const totalWidth = this._totalWidth; const totalWidth = this._totalWidth;
const progressWidth = (this._streamPercentage || 0) * totalWidth; const progressWidth = (this._streamPercentage || 0) * totalWidth;
const borderSize = (progressWidth === totalWidth) ? "0px" : "1px"; const borderSize = (progressWidth === totalWidth) ? "0px" : "1px";
const $bg = $topicProgress.find('.bg'); const $bg = $topicProgress.find('.bg');
if ($bg.length === 0) { if ($bg.length === 0) {
const style = `border-right-width: ${borderSize}; width: ${progressWidth}px`; const style = `border-right-width: ${borderSize}; width: ${progressWidth}px`;
@ -106,11 +116,10 @@ export default Ember.Component.extend({
}, },
_dock() { _dock() {
const maximumOffset = $('#topic-footer-buttons').offset(), const maximumOffset = $('#topic-bottom').offset(),
composerHeight = $('#reply-control').height() || 0, composerHeight = $('#reply-control').height() || 0,
$topicProgressWrapper = this.$(), $topicProgressWrapper = this.$(),
offset = window.pageYOffset || $('html').scrollTop(), offset = window.pageYOffset || $('html').scrollTop();
topicProgressHeight = $('#topic-progress').height();
if (!$topicProgressWrapper || $topicProgressWrapper.length === 0) { if (!$topicProgressWrapper || $topicProgressWrapper.length === 0) {
return; return;
@ -120,7 +129,13 @@ export default Ember.Component.extend({
if (maximumOffset) { if (maximumOffset) {
const threshold = maximumOffset.top; const threshold = maximumOffset.top;
const windowHeight = $(window).height(); const windowHeight = $(window).height();
isDocked = offset >= threshold - windowHeight + topicProgressHeight + composerHeight; const headerHeight = $('header').outerHeight(true);
if (this.capabilities.isIOS) {
isDocked = offset >= (threshold - windowHeight - headerHeight + composerHeight);
} else {
isDocked = offset >= (threshold - windowHeight + composerHeight);
}
} }
const dockPos = $(document).height() - $('#topic-bottom').offset().top; const dockPos = $(document).height() - $('#topic-bottom').offset().top;

View File

@ -0,0 +1,5 @@
import KeyEnterEscape from 'discourse/mixins/key-enter-escape';
export default Ember.Component.extend(KeyEnterEscape, {
elementId: 'topic-title',
});

View File

@ -1,6 +1,7 @@
import LoadMore from "discourse/mixins/load-more"; import LoadMore from "discourse/mixins/load-more";
import ClickTrack from 'discourse/lib/click-track'; import ClickTrack from 'discourse/lib/click-track';
import { selectedText } from 'discourse/lib/utilities'; import { selectedText } from 'discourse/lib/utilities';
import Post from 'discourse/models/post';
export default Ember.Component.extend(LoadMore, { export default Ember.Component.extend(LoadMore, {
loading: false, loading: false,
@ -44,6 +45,13 @@ export default Ember.Component.extend(LoadMore, {
}.on('willDestroyElement'), }.on('willDestroyElement'),
actions: { actions: {
removeBookmark(userAction) {
const stream = this.get('stream');
Post.updateBookmark(userAction.get("post_id"), false).then(() => {
stream.remove(userAction);
});
},
loadMore() { loadMore() {
if (this.get('loading')) { return; } if (this.get('loading')) { return; }

View File

@ -277,7 +277,17 @@ export default Ember.Controller.extend({
// Toggle the reply view // Toggle the reply view
toggle() { toggle() {
this.toggle(); this.closeAutocomplete();
if (this.get('model.composeState') === Composer.OPEN) {
if (Ember.isEmpty(this.get('model.reply')) && Ember.isEmpty(this.get('model.title'))) {
this.close();
} else {
this.shrink();
}
} else {
this.close();
}
return false;
}, },
togglePreview() { togglePreview() {
@ -330,6 +340,11 @@ export default Ember.Controller.extend({
}, },
hitEsc() { hitEsc() {
if (Ember.$(".emoji-picker-modal").length === 1) {
this.appEvents.trigger('emoji-picker:close');
return;
}
if ((this.get('messageCount') || 0) > 0) { if ((this.get('messageCount') || 0) > 0) {
this.appEvents.trigger('composer-messages:close'); this.appEvents.trigger('composer-messages:close');
return; return;
@ -385,20 +400,6 @@ export default Ember.Controller.extend({
return Discourse.Category.list(); return Discourse.Category.list();
}.property(), }.property(),
toggle() {
this.closeAutocomplete();
if (this.get('model.composeState') === Composer.OPEN) {
if (Ember.isEmpty(this.get('model.reply')) && Ember.isEmpty(this.get('model.title'))) {
this.close();
} else {
this.shrink();
}
} else {
this.close();
}
return false;
},
disableSubmit: Ember.computed.or("model.loading", "isUploading"), disableSubmit: Ember.computed.or("model.loading", "isUploading"),
save(force) { save(force) {

View File

@ -7,9 +7,10 @@ import InputValidation from 'discourse/models/input-validation';
import PasswordValidation from "discourse/mixins/password-validation"; import PasswordValidation from "discourse/mixins/password-validation";
import UsernameValidation from "discourse/mixins/username-validation"; import UsernameValidation from "discourse/mixins/username-validation";
import NameValidation from "discourse/mixins/name-validation"; import NameValidation from "discourse/mixins/name-validation";
import UserFieldsValidation from "discourse/mixins/user-fields-validation";
import { userPath } from 'discourse/lib/url'; import { userPath } from 'discourse/lib/url';
export default Ember.Controller.extend(ModalFunctionality, PasswordValidation, UsernameValidation, NameValidation, { export default Ember.Controller.extend(ModalFunctionality, PasswordValidation, UsernameValidation, NameValidation, UserFieldsValidation, {
login: Ember.inject.controller(), login: Ember.inject.controller(),
complete: false, complete: false,
@ -50,19 +51,10 @@ export default Ember.Controller.extend(ModalFunctionality, PasswordValidation, U
if (this.get('emailValidation.failed')) return true; if (this.get('emailValidation.failed')) return true;
if (this.get('usernameValidation.failed')) return true; if (this.get('usernameValidation.failed')) return true;
if (this.get('passwordValidation.failed')) return true; if (this.get('passwordValidation.failed')) return true;
if (this.get('userFieldsValidation.failed')) return true;
// Validate required fields
let userFields = this.get('userFields');
if (userFields) { userFields = userFields.filterBy('field.required'); }
if (!Ember.isEmpty(userFields)) {
const anyEmpty = userFields.any(function(uf) {
const val = uf.get('value');
return !val || Ember.isEmpty(val);
});
if (anyEmpty) { return true; }
}
return false; return false;
}.property('passwordRequired', 'nameValidation.failed', 'emailValidation.failed', 'usernameValidation.failed', 'passwordValidation.failed', 'formSubmitted', 'userFields.@each.value'), }.property('passwordRequired', 'nameValidation.failed', 'emailValidation.failed', 'usernameValidation.failed', 'passwordValidation.failed', 'userFieldsValidation.failed', 'formSubmitted'),
usernameRequired: Ember.computed.not('authOptions.omit_username'), usernameRequired: Ember.computed.not('authOptions.omit_username'),
@ -82,10 +74,6 @@ export default Ember.Controller.extend(ModalFunctionality, PasswordValidation, U
}); });
}.property(), }.property(),
nameInstructions: function() {
return I18n.t(Discourse.SiteSettings.full_name_required ? 'user.name.instructions_required' : 'user.name.instructions');
}.property(),
// Check the email address // Check the email address
emailValidation: function() { emailValidation: function() {
// If blank, fail without a reason // If blank, fail without a reason
@ -212,18 +200,6 @@ export default Ember.Controller.extend(ModalFunctionality, PasswordValidation, U
return self.flash(I18n.t('create_account.failed'), 'error'); return self.flash(I18n.t('create_account.failed'), 'error');
}); });
} }
}, }
_createUserFields: function() {
if (!this.site) { return; }
let userFields = this.site.get('user_fields');
if (userFields) {
userFields = _.sortBy(userFields, 'position').map(function(f) {
return Ember.Object.create({ value: null, field: f });
});
}
this.set('userFields', userFields);
}.on('init')
}); });

View File

@ -4,6 +4,7 @@ import BulkTopicSelection from 'discourse/mixins/bulk-topic-selection';
import { endWith } from 'discourse/lib/computed'; import { endWith } from 'discourse/lib/computed';
import showModal from 'discourse/lib/show-modal'; import showModal from 'discourse/lib/show-modal';
import { userPath } from 'discourse/lib/url'; import { userPath } from 'discourse/lib/url';
import TopicList from 'discourse/models/topic-list';
const controllerOpts = { const controllerOpts = {
discovery: Ember.inject.controller(), discovery: Ember.inject.controller(),
@ -60,7 +61,6 @@ const controllerOpts = {
this.topicTrackingState.resetTracking(); this.topicTrackingState.resetTracking();
this.store.findFiltered('topicList', {filter}).then(list => { this.store.findFiltered('topicList', {filter}).then(list => {
const TopicList = require('discourse/models/topic-list').default;
TopicList.hideUniformCategory(list, this.get('category')); TopicList.hideUniformCategory(list, this.get('category'));
this.setProperties({ model: list }); this.setProperties({ model: list });

View File

@ -5,7 +5,7 @@ import { popupAjaxError } from 'discourse/lib/ajax-error';
export const CLOSE_STATUS_TYPE = 'close'; export const CLOSE_STATUS_TYPE = 'close';
const OPEN_STATUS_TYPE = 'open'; const OPEN_STATUS_TYPE = 'open';
const PUBLISH_TO_CATEGORY_STATUS_TYPE = 'publish_to_category'; export const PUBLISH_TO_CATEGORY_STATUS_TYPE = 'publish_to_category';
const DELETE_STATUS_TYPE = 'delete'; const DELETE_STATUS_TYPE = 'delete';
const REMINDER_TYPE = 'reminder'; const REMINDER_TYPE = 'reminder';
@ -33,9 +33,11 @@ export default Ember.Controller.extend(ModalFunctionality, {
]; ];
}, },
@computed('updateTime', 'loading') @computed('updateTime', 'loading', 'publishToCategory', 'topicTimer.category_id')
saveDisabled(updateTime, loading) { saveDisabled(updateTime, loading, publishToCategory, topicTimerCategoryId) {
return Ember.isEmpty(updateTime) || loading; return Ember.isEmpty(updateTime) ||
loading ||
(publishToCategory && !topicTimerCategoryId);
}, },
@computed("model.visible") @computed("model.visible")
@ -70,7 +72,7 @@ export default Ember.Controller.extend(ModalFunctionality, {
time, time,
this.get('topicTimer.based_on_last_post'), this.get('topicTimer.based_on_last_post'),
statusType, statusType,
this.get('categoryId') this.get('topicTimer.category_id')
).then(result => { ).then(result => {
if (time) { if (time) {
this.send('closeModal'); this.send('closeModal');

View File

@ -158,7 +158,7 @@ export default Ember.Controller.extend(ModalFunctionality, {
fetchUserDetails() { fetchUserDetails() {
if (Discourse.User.currentProp('staff') && this.get('model.username')) { if (Discourse.User.currentProp('staff') && this.get('model.username')) {
const AdminUser = require('admin/models/admin-user').default; const AdminUser = requirejs('admin/models/admin-user').default;
AdminUser.find(this.get('model.user_id')).then(user => this.set('userDetails', user)); AdminUser.find(this.get('model.user_id')).then(user => this.set('userDetails', user));
} }
} }

View File

@ -5,6 +5,8 @@ import { extractError } from 'discourse/lib/ajax-error';
import computed from 'ember-addons/ember-computed-decorators'; import computed from 'ember-addons/ember-computed-decorators';
export default Ember.Controller.extend(ModalFunctionality, { export default Ember.Controller.extend(ModalFunctionality, {
offerHelp: null,
helpSeen: false,
@computed('accountEmailOrUsername', 'disabled') @computed('accountEmailOrUsername', 'disabled')
submitDisabled(accountEmailOrUsername, disabled) { submitDisabled(accountEmailOrUsername, disabled) {
@ -35,8 +37,7 @@ export default Ember.Controller.extend(ModalFunctionality, {
if (data.user_found === true) { if (data.user_found === true) {
key += '_found'; key += '_found';
this.set('accountEmailOrUsername', ''); this.set('accountEmailOrUsername', '');
bootbox.alert(I18n.t(key, {email: escaped, username: escaped})); this.set('offerHelp', I18n.t(key, {email: escaped, username: escaped}));
this.send("closeModal");
} else { } else {
if (data.user_found === false) { if (data.user_found === false) {
key += '_not_found'; key += '_not_found';
@ -52,6 +53,14 @@ export default Ember.Controller.extend(ModalFunctionality, {
}); });
return false; return false;
},
ok() {
this.send('closeModal');
},
help() {
this.setProperties({ offerHelp: I18n.t('forgot_password.help'), helpSeen: true });
} }
} }

View File

@ -46,6 +46,14 @@ export default Ember.Controller.extend({
return Em.isEmpty(q); return Em.isEmpty(q);
}, },
@computed('q')
highlightQuery(q) {
if (!q) { return; }
// remove l which can be used for sorting
return _.reject(q.split(/\s+/), t => t === 'l').join(' ');
},
@computed('skip_context', 'context') @computed('skip_context', 'context')
searchContextEnabled: { searchContextEnabled: {
get(skip,context){ get(skip,context){
@ -186,6 +194,11 @@ export default Ember.Controller.extend({
ajax("/search", { data: args }).then(results => { ajax("/search", { data: args }).then(results => {
const model = translateResults(results) || {}; const model = translateResults(results) || {};
if (results.grouped_search_result) {
this.set('q', results.grouped_search_result.term);
}
setTransient('lastSearch', { searchKey, model }, 5); setTransient('lastSearch', { searchKey, model }, 5);
this.set("model", model); this.set("model", model);
}).finally(() => this.set("searching", false)); }).finally(() => this.set("searching", false));

View File

@ -1,6 +1,8 @@
import ModalFunctionality from 'discourse/mixins/modal-functionality'; import ModalFunctionality from 'discourse/mixins/modal-functionality';
import { emailValid } from 'discourse/lib/utilities'; import { emailValid } from 'discourse/lib/utilities';
import computed from 'ember-addons/ember-computed-decorators'; import computed from 'ember-addons/ember-computed-decorators';
import Group from 'discourse/models/group';
import Invite from 'discourse/models/invite';
export default Ember.Controller.extend(ModalFunctionality, { export default Ember.Controller.extend(ModalFunctionality, {
userInvitedShow: Ember.inject.controller('user-invited-show'), userInvitedShow: Ember.inject.controller('user-invited-show'),
@ -11,6 +13,7 @@ export default Ember.Controller.extend(ModalFunctionality, {
hasCustomMessage: false, hasCustomMessage: false,
customMessage: null, customMessage: null,
inviteIcon: "envelope", inviteIcon: "envelope",
invitingExistingUserToTopic: false,
@computed('isMessage', 'invitingToTopic') @computed('isMessage', 'invitingToTopic')
title(isMessage, invitingToTopic) { title(isMessage, invitingToTopic) {
@ -23,9 +26,10 @@ export default Ember.Controller.extend(ModalFunctionality, {
} }
}, },
isAdmin: function(){ @computed
return Discourse.User.currentProp("admin"); isAdmin() {
}.property(), return this.currentUser.admin;
},
@computed('isAdmin', 'emailOrUsername', 'invitingToTopic', 'isPrivateTopic', 'model.groupNames', 'model.saving', 'model.details.can_invite_to') @computed('isAdmin', 'emailOrUsername', 'invitingToTopic', 'isPrivateTopic', 'model.groupNames', 'model.saving', 'model.details.can_invite_to')
disabled(isAdmin, emailOrUsername, invitingToTopic, isPrivateTopic, groupNames, saving, can_invite_to) { disabled(isAdmin, emailOrUsername, invitingToTopic, isPrivateTopic, groupNames, saving, can_invite_to) {
@ -44,29 +48,32 @@ export default Ember.Controller.extend(ModalFunctionality, {
return false; return false;
}, },
disabledCopyLink: function() { @computed('isAdmin', 'emailOrUsername', 'model.saving', 'isPrivateTopic', 'model.groupNames', 'hasCustomMessage')
if (this.get('hasCustomMessage')) return true; disabledCopyLink(isAdmin, emailOrUsername, saving, isPrivateTopic, groupNames, hasCustomMessage) {
if (this.get('model.saving')) return true; if (hasCustomMessage) return true;
if (Ember.isEmpty(this.get('emailOrUsername'))) return true; if (saving) return true;
const emailOrUsername = this.get('emailOrUsername').trim(); if (Ember.isEmpty(emailOrUsername)) return true;
const email = emailOrUsername.trim();
// email must be valid // email must be valid
if (!emailValid(emailOrUsername)) return true; if (!emailValid(email)) return true;
// normal users (not admin) can't invite users to private topic via email // normal users (not admin) can't invite users to private topic via email
if (!this.get('isAdmin') && this.get('isPrivateTopic') && emailValid(emailOrUsername)) return true; if (!isAdmin && isPrivateTopic && emailValid(email)) return true;
// when inviting to private topic via email, group name must be specified // when inviting to private topic via email, group name must be specified
if (this.get('isPrivateTopic') && Ember.isEmpty(this.get('model.groupNames')) && emailValid(emailOrUsername)) return true; if (isPrivateTopic && Ember.isEmpty(groupNames) && emailValid(email)) return true;
return false; return false;
}.property('emailOrUsername', 'model.saving', 'isPrivateTopic', 'model.groupNames', 'hasCustomMessage'), },
buttonTitle: function() { @computed('model.saving')
return this.get('model.saving') ? 'topic.inviting' : 'topic.invite_reply.action'; buttonTitle(saving) {
}.property('model.saving'), return saving ? 'topic.inviting' : 'topic.invite_reply.action';
},
// We are inviting to a topic if the model isn't the current user. // We are inviting to a topic if the model isn't the current user.
// The current user would mean we are inviting to the forum in general. // The current user would mean we are inviting to the forum in general.
invitingToTopic: function() { @computed('model')
return this.get('model') !== this.currentUser; invitingToTopic(model) {
}.property('model'), return model !== this.currentUser;
},
@computed('model', 'model.details.can_invite_via_email') @computed('model', 'model.details.can_invite_via_email')
canInviteViaEmail(model, can_invite_via_email) { canInviteViaEmail(model, can_invite_via_email) {
@ -89,14 +96,17 @@ export default Ember.Controller.extend(ModalFunctionality, {
isMessage: Em.computed.equal('model.archetype', 'private_message'), isMessage: Em.computed.equal('model.archetype', 'private_message'),
// Allow Existing Members? (username autocomplete) // Allow Existing Members? (username autocomplete)
allowExistingMembers: function() { allowExistingMembers: Ember.computed.alias('invitingToTopic'),
return this.get('invitingToTopic');
}.property('invitingToTopic'), @computed("isAdmin", "model.group_users")
isGroupOwnerOrAdmin(isAdmin, groupUsers) {
return isAdmin || (groupUsers && groupUsers.some(groupUser => groupUser.owner));
},
// Show Groups? (add invited user to private group) // Show Groups? (add invited user to private group)
@computed('isAdmin', 'emailOrUsername', 'isPrivateTopic', 'isMessage', 'invitingToTopic', 'canInviteViaEmail') @computed('isGroupOwnerOrAdmin', 'emailOrUsername', 'isPrivateTopic', 'isMessage', 'invitingToTopic', 'canInviteViaEmail')
showGroups(isAdmin, emailOrUsername, isPrivateTopic, isMessage, invitingToTopic, canInviteViaEmail) { showGroups(isGroupOwnerOrAdmin, emailOrUsername, isPrivateTopic, isMessage, invitingToTopic, canInviteViaEmail) {
return isAdmin && return isGroupOwnerOrAdmin &&
canInviteViaEmail && canInviteViaEmail &&
!isMessage && !isMessage &&
(emailValid(emailOrUsername) || isPrivateTopic || !invitingToTopic); (emailValid(emailOrUsername) || isPrivateTopic || !invitingToTopic);
@ -139,30 +149,34 @@ export default Ember.Controller.extend(ModalFunctionality, {
} }
}, },
showGroupsClass: function() { @computed('isPrivateTopic')
return this.get('isPrivateTopic') ? 'required' : 'optional'; showGroupsClass(isPrivateTopic) {
}.property('isPrivateTopic'), return isPrivateTopic ? 'required' : 'optional';
groupFinder(term) {
const Group = require('discourse/models/group').default;
return Group.findAll({search: term, ignore_automatic: true});
}, },
successMessage: function() { groupFinder(term) {
return Group.findAll({ term: term, ignore_automatic: true });
},
@computed('isMessage', 'emailOrUsername', 'invitingExistingUserToTopic')
successMessage(isMessage, emailOrUsername, invitingExistingUserToTopic) {
if (this.get('hasGroups')) { if (this.get('hasGroups')) {
return I18n.t('topic.invite_private.success_group'); return I18n.t('topic.invite_private.success_group');
} else if (this.get('isMessage')) { } else if (isMessage) {
return I18n.t('topic.invite_private.success'); return I18n.t('topic.invite_private.success');
} else if ( emailValid(this.get('emailOrUsername')) ) { } else if (invitingExistingUserToTopic) {
return I18n.t('topic.invite_reply.success_email', { emailOrUsername: this.get('emailOrUsername') }); return I18n.t('topic.invite_reply.success_existing_email', { emailOrUsername });
} else if (emailValid(emailOrUsername)) {
return I18n.t('topic.invite_reply.success_email', { emailOrUsername });
} else { } else {
return I18n.t('topic.invite_reply.success_username'); return I18n.t('topic.invite_reply.success_username');
} }
}.property('model.inviteLink', 'isMessage', 'emailOrUsername'), },
errorMessage: function() { @computed('isMessage')
return this.get('isMessage') ? I18n.t('topic.invite_private.error') : I18n.t('topic.invite_reply.error'); errorMessage(isMessage) {
}.property('isMessage'), return isMessage ? I18n.t('topic.invite_private.error') : I18n.t('topic.invite_reply.error');
},
@computed('canInviteViaEmail') @computed('canInviteViaEmail')
placeholderKey(canInviteViaEmail) { placeholderKey(canInviteViaEmail) {
@ -171,15 +185,17 @@ export default Ember.Controller.extend(ModalFunctionality, {
'topic.invite_reply.username_placeholder'; 'topic.invite_reply.username_placeholder';
}, },
customMessagePlaceholder: function() { @computed
customMessagePlaceholder() {
return I18n.t('invite.custom_message_placeholder'); return I18n.t('invite.custom_message_placeholder');
}.property(), },
// Reset the modal to allow a new user to be invited. // Reset the modal to allow a new user to be invited.
reset() { reset() {
this.set('emailOrUsername', null); this.set('emailOrUsername', null);
this.set('hasCustomMessage', false); this.set('hasCustomMessage', false);
this.set('customMessage', null); this.set('customMessage', null);
this.set('invitingExistingUserToTopic', false);
this.get('model').setProperties({ this.get('model').setProperties({
groupNames: null, groupNames: null,
error: false, error: false,
@ -188,12 +204,11 @@ export default Ember.Controller.extend(ModalFunctionality, {
inviteLink: null inviteLink: null
}); });
}, },
actions: { actions: {
createInvite() { createInvite() {
const Invite = require('discourse/models/invite').default;
const self = this; const self = this;
if (this.get('disabled')) { return; } if (this.get('disabled')) { return; }
const groupNames = this.get('model.groupNames'), const groupNames = this.get('model.groupNames'),
@ -231,13 +246,14 @@ export default Ember.Controller.extend(ModalFunctionality, {
} else if (this.get('isMessage') && result && result.user) { } else if (this.get('isMessage') && result && result.user) {
this.get('model.details.allowed_users').pushObject(Ember.Object.create(result.user)); this.get('model.details.allowed_users').pushObject(Ember.Object.create(result.user));
this.appEvents.trigger('post-stream:refresh'); this.appEvents.trigger('post-stream:refresh');
} else if (this.get('invitingToTopic') && emailValid(this.get('emailOrUsername').trim()) && result && result.user) {
this.set('invitingExistingUserToTopic', true);
} }
}).catch(onerror); }).catch(onerror);
} }
}, },
generateInvitelink() { generateInvitelink() {
const Invite = require('discourse/models/invite').default;
const self = this; const self = this;
if (this.get('disabled')) { return; } if (this.get('disabled')) { return; }

View File

@ -5,15 +5,17 @@ import { ajax } from 'discourse/lib/ajax';
import PasswordValidation from "discourse/mixins/password-validation"; import PasswordValidation from "discourse/mixins/password-validation";
import UsernameValidation from "discourse/mixins/username-validation"; import UsernameValidation from "discourse/mixins/username-validation";
import NameValidation from "discourse/mixins/name-validation"; import NameValidation from "discourse/mixins/name-validation";
import UserFieldsValidation from "discourse/mixins/user-fields-validation";
import { findAll as findLoginMethods } from 'discourse/models/login-method'; import { findAll as findLoginMethods } from 'discourse/models/login-method';
export default Ember.Controller.extend(PasswordValidation, UsernameValidation, NameValidation, { export default Ember.Controller.extend(PasswordValidation, UsernameValidation, NameValidation, UserFieldsValidation, {
invitedBy: Ember.computed.alias('model.invited_by'), invitedBy: Ember.computed.alias('model.invited_by'),
email: Ember.computed.alias('model.email'), email: Ember.computed.alias('model.email'),
accountUsername: Ember.computed.alias('model.username'), accountUsername: Ember.computed.alias('model.username'),
passwordRequired: Ember.computed.notEmpty('accountPassword'), passwordRequired: Ember.computed.notEmpty('accountPassword'),
successMessage: null, successMessage: null,
errorMessage: null, errorMessage: null,
userFields: null,
inviteImageUrl: getUrl('/images/envelope.svg'), inviteImageUrl: getUrl('/images/envelope.svg'),
@computed @computed
@ -21,11 +23,6 @@ export default Ember.Controller.extend(PasswordValidation, UsernameValidation, N
return I18n.t('invites.welcome_to', {site_name: this.siteSettings.title}); return I18n.t('invites.welcome_to', {site_name: this.siteSettings.title});
}, },
@computed
nameLabel() {
return I18n.t(this.siteSettings.full_name_required ? 'invites.name_label' : 'invites.name_label_optional');
},
@computed('email') @computed('email')
yourEmailMessage(email) { yourEmailMessage(email) {
return I18n.t('invites.your_email', {email: email}); return I18n.t('invites.your_email', {email: email});
@ -36,20 +33,30 @@ export default Ember.Controller.extend(PasswordValidation, UsernameValidation, N
return findLoginMethods(this.siteSettings, this.capabilities, this.site.isMobileDevice).length > 0; return findLoginMethods(this.siteSettings, this.capabilities, this.site.isMobileDevice).length > 0;
}, },
@computed('usernameValidation.failed', 'passwordValidation.failed', 'nameValidation.failed') @computed('usernameValidation.failed', 'passwordValidation.failed', 'nameValidation.failed', 'userFieldsValidation.failed')
submitDisabled(usernameFailed, passwordFailed, nameFailed) { submitDisabled(usernameFailed, passwordFailed, nameFailed, userFieldsFailed) {
return usernameFailed || passwordFailed || nameFailed; return usernameFailed || passwordFailed || nameFailed || userFieldsFailed;
}, },
actions: { actions: {
submit() { submit() {
const userFields = this.get('userFields');
let userCustomFields = {};
if (!Ember.isEmpty(userFields)) {
userFields.forEach(function(f) {
userCustomFields[f.get('field.id')] = f.get('value');
});
}
ajax({ ajax({
url: `/invites/show/${this.get('model.token')}.json`, url: `/invites/show/${this.get('model.token')}.json`,
type: 'PUT', type: 'PUT',
data: { data: {
username: this.get('accountUsername'), username: this.get('accountUsername'),
name: this.get('accountName'), name: this.get('accountName'),
password: this.get('accountPassword') password: this.get('accountPassword'),
userCustomFields
} }
}).then(result => { }).then(result => {
if (result.success) { if (result.success) {

View File

@ -47,8 +47,6 @@ export default Ember.Controller.extend(PreferencesTabController, {
const model = this.get('model'), const model = this.get('model'),
userFields = this.get('userFields'); userFields = this.get('userFields');
model.set('name', this.get('newNameInput'));
// Update the user fields // Update the user fields
if (!Ember.isEmpty(userFields)) { if (!Ember.isEmpty(userFields)) {
const modelFields = model.get('user_fields'); const modelFields = model.get('user_fields');

View File

@ -1,11 +1,12 @@
import { default as computed, observes } from 'ember-addons/ember-computed-decorators';
import { setting, propertyEqual } from 'discourse/lib/computed'; import { setting, propertyEqual } from 'discourse/lib/computed';
import DiscourseURL from 'discourse/lib/url'; import DiscourseURL from 'discourse/lib/url';
import { userPath } from 'discourse/lib/url'; import { userPath } from 'discourse/lib/url';
import { popupAjaxError } from 'discourse/lib/ajax-error';
export default Ember.Controller.extend({ export default Ember.Controller.extend({
taken: false, taken: false,
saving: false, saving: false,
error: false,
errorMessage: null, errorMessage: null,
newUsername: null, newUsername: null,
@ -15,29 +16,34 @@ export default Ember.Controller.extend({
saveDisabled: Em.computed.or('saving', 'newUsernameEmpty', 'taken', 'unchanged', 'errorMessage'), saveDisabled: Em.computed.or('saving', 'newUsernameEmpty', 'taken', 'unchanged', 'errorMessage'),
unchanged: propertyEqual('newUsername', 'username'), unchanged: propertyEqual('newUsername', 'username'),
checkTaken: function() { @observes("newUsername")
if( this.get('newUsername') && this.get('newUsername').length < this.get('minLength') ) { checkTaken() {
let newUsername = this.get('newUsername');
if (newUsername && newUsername.length < this.get('minLength')) {
this.set('errorMessage', I18n.t('user.name.too_short')); this.set('errorMessage', I18n.t('user.name.too_short'));
} else { } else {
var self = this;
this.set('taken', false); this.set('taken', false);
this.set('errorMessage', null); this.set('errorMessage', null);
if (Ember.isEmpty(this.get('newUsername'))) return; if (Ember.isEmpty(this.get('newUsername'))) return;
if (this.get('unchanged')) return; if (this.get('unchanged')) return;
Discourse.User.checkUsername(this.get('newUsername'), undefined, this.get('content.id')).then(function(result) {
Discourse.User.checkUsername(newUsername, undefined, this.get('content.id')).then(result => {
if (result.errors) { if (result.errors) {
self.set('errorMessage', result.errors.join(' ')); this.set('errorMessage', result.errors.join(' '));
} else if (result.available === false) { } else if (result.available === false) {
self.set('taken', true); this.set('taken', true);
} }
}); });
} }
}.observes('newUsername'), },
saveButtonText: function() { @computed('saving')
if (this.get('saving')) return I18n.t("saving"); saveButtonText(saving) {
if (saving) return I18n.t("saving");
return I18n.t("user.change"); return I18n.t("user.change");
}.property('saving'), },
actions: { actions: {
changeUsername() { changeUsername() {
@ -51,7 +57,7 @@ export default Ember.Controller.extend({
this.get('content').changeUsername(this.get('newUsername')).then(() => { this.get('content').changeUsername(this.get('newUsername')).then(() => {
DiscourseURL.redirectTo(userPath(this.get('newUsername').toLowerCase() + "/preferences")); DiscourseURL.redirectTo(userPath(this.get('newUsername').toLowerCase() + "/preferences"));
}) })
.catch(() => this.set('error', true)) .catch(popupAjaxError)
.finally(() => this.set('saving', false)); .finally(() => this.set('saving', false));
} }
}); });
@ -59,5 +65,3 @@ export default Ember.Controller.extend({
} }
}); });

View File

@ -4,6 +4,9 @@ const _buttons = [];
const alwaysTrue = () => true; const alwaysTrue = () => true;
function identity() {
}
function addBulkButton(action, key, opts) { function addBulkButton(action, key, opts) {
opts = opts || {}; opts = opts || {};
@ -72,7 +75,7 @@ export default Ember.Controller.extend(ModalFunctionality, {
this.perform(operation).then(topics => { this.perform(operation).then(topics => {
if (topics) { if (topics) {
topics.forEach(cb); topics.forEach(cb);
(this.get('refreshClosure') || Ember.k)(); (this.get('refreshClosure') || identity)();
this.send('closeModal'); this.send('closeModal');
} }
}); });
@ -80,7 +83,7 @@ export default Ember.Controller.extend(ModalFunctionality, {
performAndRefresh(operation) { performAndRefresh(operation) {
return this.perform(operation).then(() => { return this.perform(operation).then(() => {
(this.get('refreshClosure') || Ember.k)(); (this.get('refreshClosure') || identity)();
this.send('closeModal'); this.send('closeModal');
}); });
}, },
@ -145,7 +148,7 @@ export default Ember.Controller.extend(ModalFunctionality, {
this.perform({type: 'change_category', category_id: categoryId}).then(topics => { this.perform({type: 'change_category', category_id: categoryId}).then(topics => {
topics.forEach(t => t.set('category', category)); topics.forEach(t => t.set('category', category));
(this.get('refreshClosure') || Ember.k)(); (this.get('refreshClosure') || identity)();
this.send('closeModal'); this.send('closeModal');
}); });
}, },

View File

@ -196,7 +196,7 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
const quotedText = Quote.build(post, buffer); const quotedText = Quote.build(post, buffer);
composerOpts.quote = quotedText; composerOpts.quote = quotedText;
if (composer.get('model.viewOpen')) { if (composer.get('model.viewOpen')) {
this.appEvents.trigger('composer:insert-text', quotedText); this.appEvents.trigger('composer:insert-block', quotedText);
} else if (composer.get('model.viewDraft')) { } else if (composer.get('model.viewDraft')) {
const model = composer.get('model'); const model = composer.get('model');
model.set('reply', model.get('reply') + quotedText); model.set('reply', model.get('reply') + quotedText);
@ -320,7 +320,7 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
composerController.get('content.action') === Composer.REPLY) { composerController.get('content.action') === Composer.REPLY) {
composerController.set('content.post', post); composerController.set('content.post', post);
composerController.set('content.composeState', Composer.OPEN); composerController.set('content.composeState', Composer.OPEN);
this.appEvents.trigger('composer:insert-text', quotedText.trim()); this.appEvents.trigger('composer:insert-block', quotedText.trim());
} else { } else {
const opts = { const opts = {

View File

@ -12,6 +12,7 @@ export default Ember.Controller.extend({
canLoadMore: true, canLoadMore: true,
invitesLoading: false, invitesLoading: false,
reinvitedAll: false, reinvitedAll: false,
rescindedAll: false,
init: function() { init: function() {
this._super(); this._super();
@ -32,7 +33,7 @@ export default Ember.Controller.extend({
inviteRedeemed: Em.computed.equal('filter', 'redeemed'), inviteRedeemed: Em.computed.equal('filter', 'redeemed'),
showReinviteAllButton: function() { showBulkActionButtons: function() {
return (this.get('filter') === "pending" && this.get('model').invites.length > 4 && this.currentUser.get('staff')); return (this.get('filter') === "pending" && this.get('model').invites.length > 4 && this.currentUser.get('staff'));
}.property('filter'), }.property('filter'),
@ -86,17 +87,27 @@ export default Ember.Controller.extend({
return false; return false;
}, },
rescindAll() {
bootbox.confirm(I18n.t("user.invited.rescind_all_confirm"), confirm => {
if (confirm) {
Invite.rescindAll().then(() => {
this.set('rescindedAll', true);
this.get('model.invites').clear();
}).catch(popupAjaxError);
}
});
},
reinvite(invite) { reinvite(invite) {
invite.reinvite(); invite.reinvite();
return false; return false;
}, },
reinviteAll() { reinviteAll() {
const self = this;
bootbox.confirm(I18n.t("user.invited.reinvite_all_confirm"), confirm => { bootbox.confirm(I18n.t("user.invited.reinvite_all_confirm"), confirm => {
if (confirm) { if (confirm) {
Invite.reinviteAll().then(function() { Invite.reinviteAll().then(() => {
self.set('reinvitedAll', true); this.set('reinvitedAll', true);
}).catch(popupAjaxError); }).catch(popupAjaxError);
} }
}); });

View File

@ -87,7 +87,7 @@ export default Ember.Controller.extend(CanCheckEmails, {
adminDelete() { adminDelete() {
// I really want this deferred, don't want to bring in all this code till used // I really want this deferred, don't want to bring in all this code till used
const AdminUser = require('admin/models/admin-user').default; const AdminUser = requirejs('admin/models/admin-user').default;
AdminUser.find(this.get('model.id')).then(user => user.destroy({deletePosts: true})); AdminUser.find(this.get('model.id')).then(user => user.destroy({deletePosts: true}));
}, },

View File

@ -1,4 +0,0 @@
import { cook } from 'discourse/lib/text';
import { registerUnbound } from 'discourse-common/lib/helpers';
registerUnbound('cook-text', cook);

View File

@ -20,7 +20,7 @@ function renderRaw(ctx, container, template, templateName, params) {
const module = `discourse/raw-views/${templateName}`; const module = `discourse/raw-views/${templateName}`;
if (requirejs.entries[module]) { if (requirejs.entries[module]) {
const viewClass = require(module, null, null, true); const viewClass = requirejs(module, null, null, true);
if (viewClass && viewClass.default) { if (viewClass && viewClass.default) {
params.view = viewClass.default.create(params, _injections); params.view = viewClass.default.create(params, _injections);
} }

View File

@ -2,11 +2,11 @@ import { registerHelpers } from 'discourse-common/lib/helpers';
export function autoLoadModules(container, registry) { export function autoLoadModules(container, registry) {
Object.keys(requirejs.entries).forEach(entry => { Object.keys(requirejs.entries).forEach(entry => {
if ((/\/helpers\//).test(entry)) { if ((/\/helpers\//).test(entry) && !(/-test/).test(entry)) {
require(entry, null, null, true); requirejs(entry, null, null, true);
} }
if ((/\/widgets\//).test(entry)) { if ((/\/widgets\//).test(entry) && !(/-test/).test(entry)) {
require(entry, null, null, true); requirejs(entry, null, null, true);
} }
}); });
registerHelpers(registry); registerHelpers(registry);

View File

@ -2,5 +2,5 @@
export default { export default {
name: "inject-objects", name: "inject-objects",
initialize: Ember.K initialize() { }
}; };

View File

@ -2,5 +2,5 @@
export default { export default {
name: "register-discourse-location", name: "register-discourse-location",
initialize: Ember.K initialize() { }
}; };

View File

@ -25,5 +25,8 @@ var transitionEnd = (function() {
})(); })();
export default function (element, callback) { export default function (element, callback) {
return $(element).on(transitionEnd, callback); return $(element).on(transitionEnd, event => {
if (event.target !== event.currentTarget) return;
return callback(event);
});
} }

View File

@ -358,10 +358,22 @@ export default function(options) {
$(this).on('keyup.autocomplete', function(e) { $(this).on('keyup.autocomplete', function(e) {
if ([keys.esc, keys.enter].indexOf(e.which) !== -1) return true; if ([keys.esc, keys.enter].indexOf(e.which) !== -1) return true;
var cp = caretPosition(me[0]); let cp = caretPosition(me[0]);
const key = me[0].value[cp-1];
if (options.key && completeStart === null && cp > 0) { if (options.key) {
var key = me[0].value[cp-1]; if (options.onKeyUp && key !== options.key) {
let match = options.onKeyUp(me.val(), cp);
if (match) {
completeStart = cp - match[0].length;
completeEnd = completeStart + match[0].length - 1;
let term = match[0].substring(1, match[0].length);
updateAutoComplete(dataSource(term, options));
}
}
}
if (completeStart === null && cp > 0) {
if (key === options.key) { if (key === options.key) {
var prevChar = me.val().charAt(cp-2); var prevChar = me.val().charAt(cp-2);
if (checkTriggerRule() && (!prevChar || allowedLettersRegex.test(prevChar))) { if (checkTriggerRule() && (!prevChar || allowedLettersRegex.test(prevChar))) {
@ -370,7 +382,7 @@ export default function(options) {
} }
} }
} else if (completeStart !== null) { } else if (completeStart !== null) {
var term = me.val().substring(completeStart + (options.key ? 1 : 0), cp); let term = me.val().substring(completeStart + (options.key ? 1 : 0), cp);
updateAutoComplete(dataSource(term, options)); updateAutoComplete(dataSource(term, options));
} }
}); });

View File

@ -132,15 +132,9 @@ function assign(ta, {setOverflowX = true, setOverflowY = true} = {}) {
set.delete(ta); set.delete(ta);
Object.keys(style).forEach(key => { Object.keys(style).forEach(key => {
ta.style[key] = style[key]; ta.style[key] = style[key];
}); });
}.bind(ta, { }
height: ta.style.height,
resize: ta.style.resize,
overflowY: ta.style.overflowY,
overflowX: ta.style.overflowX,
wordWrap: ta.style.wordWrap,
});
ta.addEventListener('autosize:destroy', destroy, false); ta.addEventListener('autosize:destroy', destroy, false);

View File

@ -0,0 +1,32 @@
export default class DirtyKeys {
constructor(name) {
this.name = name;
this._keys = {};
}
keyDirty(key, options) {
options = options || {};
options.dirty = true;
this._keys[key] = options;
}
forceAll() {
this.keyDirty('*');
}
allDirty() {
return !!this._keys['*'];
}
optionsFor(key) {
return this._keys[key] || { dirty: false };
}
renderedKey(key) {
if (key === '*') {
this._keys = {};
} else {
delete this._keys[key];
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,183 +0,0 @@
import groups from 'discourse/lib/emoji/groups';
import KeyValueStore from "discourse/lib/key-value-store";
import { emojiList } from 'pretty-text/emoji';
import { emojiUrlFor } from 'discourse/lib/text';
import { findRawTemplate } from 'discourse/lib/raw-templates';
const keyValueStore = new KeyValueStore("discourse_emojis_");
const EMOJI_USAGE = "emojiUsage";
let PER_ROW = 12;
const PER_PAGE = 60;
let ungroupedIcons, recentlyUsedIcons;
if (!keyValueStore.getObject(EMOJI_USAGE)) {
keyValueStore.setObject({key: EMOJI_USAGE, value: {}});
}
function closeSelector() {
$('.emoji-modal, .emoji-modal-wrapper').remove();
$('body, textarea').off('keydown.emoji');
}
function initializeUngroupedIcons() {
const groupedIcons = {};
groups.forEach(group => {
group.icons.forEach(icon => groupedIcons[icon] = true);
});
ungroupedIcons = [];
const emojis = emojiList();
emojis.forEach(emoji => {
if (groupedIcons[emoji] !== true) {
ungroupedIcons.push(emoji);
}
});
if (ungroupedIcons.length) {
groups.push({name: 'ungrouped', icons: ungroupedIcons});
}
}
function trackEmojiUsage(title) {
const recent = keyValueStore.getObject(EMOJI_USAGE) || {};
if (!recent[title]) { recent[title] = { title: title, usage: 0 }; }
recent[title]["usage"]++;
keyValueStore.setObject({key: EMOJI_USAGE, value: recent});
// clear the cache
recentlyUsedIcons = null;
}
function sortByUsage(a, b) {
if (a.usage > b.usage) { return -1; }
if (b.usage > a.usage) { return 1; }
return a.title.localeCompare(b.title);
}
function initializeRecentlyUsedIcons() {
recentlyUsedIcons = [];
const usage = _.map(keyValueStore.getObject(EMOJI_USAGE)).sort(sortByUsage);
const recent = usage.slice(0, PER_ROW);
if (recent.length > 0) {
recent.forEach(emoji => recentlyUsedIcons.push(emoji.title));
const recentGroup = groups.findBy('name', 'recent');
if (recentGroup) {
recentGroup.icons = recentlyUsedIcons;
} else {
groups.push({ name: 'recent', icons: recentlyUsedIcons });
}
}
}
function toolbar(selected) {
if (!ungroupedIcons) { initializeUngroupedIcons(); }
if (!recentlyUsedIcons) { initializeRecentlyUsedIcons(); }
return groups.map((g, i) => {
let icon = g.tabicon;
let title = g.fullname;
if (g.name === "recent") {
icon = "star";
title = "Recent";
} else if (g.name === "ungrouped") {
icon = g.icons[0];
title = "Custom";
}
return { src: emojiUrlFor(icon),
title,
groupId: i,
selected: i === selected };
});
}
function bindEvents(page, offset, options) {
$('.emoji-page a').click(e => {
const title = $(e.currentTarget).attr('title');
trackEmojiUsage(title);
options.onSelect(title);
closeSelector();
return false;
}).hover(e => {
const title = $(e.currentTarget).attr('title');
const html = "<img src='" + emojiUrlFor(title) + "' class='emoji'> <span>:" + title + ":<span>";
$('.emoji-modal .info').html(html);
}, () => $('.emoji-modal .info').html(""));
$('.emoji-modal .nav .next a').click(() => render(page, offset+PER_PAGE, options));
$('.emoji-modal .nav .prev a').click(() => render(page, offset-PER_PAGE, options));
$('.emoji-modal .toolbar a').click(function(){
const p = parseInt($(this).data('group-id'));
render(p, 0, options);
return false;
});
}
function render(page, offset, options) {
keyValueStore.set({key: "emojiPage", value: page});
keyValueStore.set({key: "emojiOffset", value: offset});
const toolbarItems = toolbar(page);
const rows = [];
let row = [];
const icons = groups[page].icons;
const max = offset + PER_PAGE;
for(let i=offset; i<max; i++){
if(!icons[i]){ break; }
if(row.length === (options.perRow || PER_ROW)){
rows.push(row);
row = [];
}
row.push({src: emojiUrlFor(icons[i]), title: icons[i]});
}
rows.push(row);
const model = {
toolbarItems: toolbarItems,
rows: rows,
prevDisabled: offset === 0,
nextDisabled: (max + 1) > icons.length,
modalClass: options.modalClass
};
$('.emoji-modal', options.appendTo).remove();
const template = findRawTemplate('emoji-toolbar');
options.appendTo.append(template(model));
bindEvents(page, offset, options);
}
function showSelector(options) {
options = options || {};
options.appendTo = options.appendTo || $('body');
options.appendTo.append('<div class="emoji-modal-wrapper"></div>');
$('.emoji-modal-wrapper').click(() => closeSelector());
if (Discourse.Site.currentProp('mobileView')) { PER_ROW = 9; }
const page = options.page ? _.findIndex(groups, (g) => { return g.name === options.page; })
: keyValueStore.getInt("emojiPage", 0);
const offset = keyValueStore.getInt("emojiOffset", 0);
render(page, offset, options);
$('body, textarea').on('keydown.emoji', e => {
if (e.which === 27) {
closeSelector();
return false;
}
});
}
export { showSelector };

View File

@ -0,0 +1,8 @@
export default function($elem, term) {
if(!_.isEmpty(term)) {
// special case ignore "l" which is used for magic sorting
let words = _.reject(term.match(/"[^"]+"|[^\s]+/g), t => t === 'l');
words = words.map(w => w.replace(/^"(.*)"$/, "$1"));
$elem.highlight(words, {className: 'search-highlight', wordsOnly: true});
}
}

View File

@ -36,7 +36,11 @@ export default function loadScript(url, opts) {
opts = opts || {}; opts = opts || {};
$('script').each((i, tag) => { $('script').each((i, tag) => {
_loaded[tag.getAttribute('src')] = true; const src = tag.getAttribute('src');
if (src && (opts.scriptTag || src !== url)) {
_loaded[tag.getAttribute('src')] = true;
}
}); });
@ -57,12 +61,12 @@ export default function loadScript(url, opts) {
}); });
const cb = function(data) { const cb = function(data) {
_loaded[url] = true;
if (opts && opts.css) { if (opts && opts.css) {
$("head").append("<style>" + data + "</style>"); $("head").append("<style>" + data + "</style>");
} }
done(); done();
resolve(); resolve();
_loaded[url] = true;
}; };
let cdnUrl = url; let cdnUrl = url;

View File

@ -22,7 +22,7 @@ import { attachAdditionalPanel } from 'discourse/widgets/header';
// If you add any methods to the API ensure you bump up this number // If you add any methods to the API ensure you bump up this number
const PLUGIN_API_VERSION = '0.8.6'; const PLUGIN_API_VERSION = '0.8.7';
class PluginApi { class PluginApi {
constructor(version, container) { constructor(version, container) {
@ -39,6 +39,25 @@ class PluginApi {
return this.container.lookup('current-user:main'); return this.container.lookup('current-user:main');
} }
/**
* Allows you to overwrite or extend methods in a class.
*
* For example:
*
* ```
* api.modifyClass('controller:composer', {
* actions: {
* newActionHere() { }
* }
* });
* ```
**/
modifyClass(resolverName, changes) {
const klass = this.container.factoryFor(resolverName);
klass.class.reopen(changes);
return klass;
}
/** /**
* Used for decorating the `cooked` content of a post after it is rendered using * Used for decorating the `cooked` content of a post after it is rendered using
* jQuery. * jQuery.
@ -61,7 +80,7 @@ class PluginApi {
if (!opts.onlyStream) { if (!opts.onlyStream) {
decorate(ComposerEditor, 'previewRefreshed', callback); decorate(ComposerEditor, 'previewRefreshed', callback);
decorate(this.container.lookupFactory('component:user-stream'), 'didInsertElement', callback); decorate(this.container.factoryFor('component:user-stream').class, 'didInsertElement', callback);
} }
} }
@ -170,7 +189,7 @@ class PluginApi {
* ``` * ```
**/ **/
attachWidgetAction(widget, actionName, fn) { attachWidgetAction(widget, actionName, fn) {
const widgetClass = this.container.lookupFactory(`widget:${widget}`); const widgetClass = this.container.factoryFor(`widget:${widget}`).class;
widgetClass.prototype[actionName] = fn; widgetClass.prototype[actionName] = fn;
} }

View File

@ -50,7 +50,7 @@ function findClass(outletName, uniqueName) {
if (!_classPaths) { if (!_classPaths) {
_classPaths = {}; _classPaths = {};
findOutlets(require._eak_seen, (outlet, res, un) => { findOutlets(require._eak_seen, (outlet, res, un) => {
_classPaths[`${outlet}/${un}`] = require(res).default; _classPaths[`${outlet}/${un}`] = requirejs(res).default;
}); });
} }

View File

@ -56,7 +56,7 @@ export default Ember.Object.extend(Ember.Array, {
}, },
finishedPrepending(postIds) { finishedPrepending(postIds) {
this._changeArray(Ember.K, 0, 0, postIds.length); this._changeArray(function() { }, 0, 0, postIds.length);
}, },
objectAt(index) { objectAt(index) {

View File

@ -123,6 +123,10 @@ function positioningWorkaround($fixedElement) {
const checkForInputs = _.debounce(function(){ const checkForInputs = _.debounce(function(){
$fixedElement.find('button:not(.hide-preview),a:not(.mobile-file-upload):not(.toggle-toolbar)').each(function(idx, elem){ $fixedElement.find('button:not(.hide-preview),a:not(.mobile-file-upload):not(.toggle-toolbar)').each(function(idx, elem){
if ($(elem).parents('.emoji-picker').length > 0) {
return;
}
if ($(elem).parents('.autocomplete').length > 0) { if ($(elem).parents('.autocomplete').length > 0) {
return; return;
} }

View File

@ -6,13 +6,11 @@ import Category from 'discourse/models/category';
import { search as searchCategoryTag } from 'discourse/lib/category-tag-search'; import { search as searchCategoryTag } from 'discourse/lib/category-tag-search';
import userSearch from 'discourse/lib/user-search'; import userSearch from 'discourse/lib/user-search';
import { userPath } from 'discourse/lib/url'; import { userPath } from 'discourse/lib/url';
import User from 'discourse/models/user';
import Post from 'discourse/models/post';
import Topic from 'discourse/models/topic';
export function translateResults(results, opts) { export function translateResults(results, opts) {
const User = require('discourse/models/user').default;
const Post = require('discourse/models/post').default;
const Topic = require('discourse/models/topic').default;
if (!opts) opts = {}; if (!opts) opts = {};
// Topics might not be included // Topics might not be included
@ -94,9 +92,9 @@ export function searchForTerm(term, opts) {
}; };
} }
var promise = ajax('/search/query', { data: data }); let promise = ajax('/search/query', { data: data });
promise.then(function(results){ promise.then(results => {
return translateResults(results, opts); return translateResults(results, opts);
}); });

View File

@ -2,24 +2,40 @@ import { default as PrettyText, buildOptions } from 'pretty-text/pretty-text';
import { performEmojiUnescape, buildEmojiUrl } from 'pretty-text/emoji'; import { performEmojiUnescape, buildEmojiUrl } from 'pretty-text/emoji';
import WhiteLister from 'pretty-text/white-lister'; import WhiteLister from 'pretty-text/white-lister';
import { sanitize as textSanitize } from 'pretty-text/sanitizer'; import { sanitize as textSanitize } from 'pretty-text/sanitizer';
import loadScript from 'discourse/lib/load-script';
function getOpts() { function getOpts(opts) {
const siteSettings = Discourse.__container__.lookup('site-settings:main'); const siteSettings = Discourse.__container__.lookup('site-settings:main');
return buildOptions({ opts = _.merge({
getURL: Discourse.getURLWithCDN, getURL: Discourse.getURLWithCDN,
currentUser: Discourse.__container__.lookup('current-user:main'), currentUser: Discourse.__container__.lookup('current-user:main'),
siteSettings siteSettings
}); }, opts);
return buildOptions(opts);
} }
// Use this to easily create a pretty text instance with proper options // Use this to easily create a pretty text instance with proper options
export function cook(text) { export function cook(text, options) {
return new Handlebars.SafeString(new PrettyText(getOpts()).cook(text)); return new Handlebars.SafeString(new PrettyText(getOpts(options)).cook(text));
} }
export function sanitize(text) { // everything should eventually move to async API and this should be renamed
return textSanitize(text, new WhiteLister(getOpts())); // cook
export function cookAsync(text, options) {
if (Discourse.MarkdownItURL) {
return loadScript(Discourse.MarkdownItURL)
.then(()=>cook(text, options))
.catch(e => Ember.Logger.error(e));
} else {
return Ember.RSVP.Promise.resolve(cook(text));
}
}
export function sanitize(text, options) {
return textSanitize(text, new WhiteLister(options));
} }
function emojiOptions() { function emojiOptions() {

View File

@ -14,6 +14,7 @@ const SERVER_SIDE_ONLY = [
/^\/raw\//, /^\/raw\//,
/^\/posts\/\d+\/raw/, /^\/posts\/\d+\/raw/,
/^\/raw\/\d+/, /^\/raw\/\d+/,
/^\/wizard/,
/\.rss$/, /\.rss$/,
/\.json$/, /\.json$/,
]; ];
@ -191,13 +192,6 @@ const DiscourseURL = Ember.Object.extend({
const oldPath = window.location.pathname; const oldPath = window.location.pathname;
path = path.replace(/(https?\:)?\/\/[^\/]+/, ''); path = path.replace(/(https?\:)?\/\/[^\/]+/, '');
// handle prefixes
if (path.match(/^\//)) {
let rootURL = (Discourse.BaseUri === undefined ? "/" : Discourse.BaseUri);
rootURL = rootURL.replace(/\/$/, '');
path = path.replace(rootURL, '');
}
// Rewrite /my/* urls // Rewrite /my/* urls
if (path.indexOf('/my/') === 0) { if (path.indexOf('/my/') === 0) {
const currentUser = Discourse.User.current(); const currentUser = Discourse.User.current();
@ -209,6 +203,13 @@ const DiscourseURL = Ember.Object.extend({
} }
} }
// handle prefixes
if (path.match(/^\//)) {
let rootURL = (Discourse.BaseUri === undefined ? "/" : Discourse.BaseUri);
rootURL = rootURL.replace(/\/$/, '');
path = path.replace(rootURL, '');
}
path = rewritePath(path); path = rewritePath(path);
if (this.navigatedToPost(oldPath, path, opts)) { return; } if (this.navigatedToPost(oldPath, path, opts)) { return; }
@ -221,6 +222,11 @@ const DiscourseURL = Ember.Object.extend({
// TODO: Extract into rules we can inject into the URL handler // TODO: Extract into rules we can inject into the URL handler
if (this.navigatedToHome(oldPath, path, opts)) { return; } if (this.navigatedToHome(oldPath, path, opts)) { return; }
// Navigating to empty string is the same as root
if (path === '') {
path = '/';
}
return this.handleURL(path, opts); return this.handleURL(path, opts);
}, },
@ -367,7 +373,7 @@ const DiscourseURL = Ember.Object.extend({
discoveryTopics.resetParams(); discoveryTopics.resetParams();
} }
router.router.updateURL(path); router._routerMicrolib.updateURL(path);
} }
const split = path.split('#'); const split = path.split('#');

View File

@ -102,8 +102,10 @@ export function selectedText() {
$div.find("img.emoji").replaceWith(function() { return this.title; }); $div.find("img.emoji").replaceWith(function() { return this.title; });
// replace br with newlines // replace br with newlines
$div.find("br").replaceWith(() => "\n"); $div.find("br").replaceWith(() => "\n");
// enforce newline at the end of paragraphs
$div.find("p").append(() => "\n");
return String($div.text()).trim(); return String($div.text()).trim().replace(/(^\s*\n)+/gm, "\n");
} }
// Determine the row and col of the caret in an element // Determine the row and col of the caret in an element
@ -172,7 +174,7 @@ export function validateUploadedFiles(files, opts) {
} }
opts = opts || {}; opts = opts || {};
opts["type"] = uploadTypeFromFileName(upload.name); opts.type = uploadTypeFromFileName(upload.name);
return validateUploadedFile(upload, opts); return validateUploadedFile(upload, opts);
} }
@ -185,12 +187,18 @@ export function validateUploadedFile(file, opts) {
if (!name) { return false; } if (!name) { return false; }
// check that the uploaded file is authorized // check that the uploaded file is authorized
if (opts["imagesOnly"]) { if (opts.allowStaffToUploadAnyFileInPm && opts.isPrivateMessage) {
if (Discourse.User.current("staff")) {
return true;
}
}
if (opts.imagesOnly) {
if (!isAnImage(name) && !isAuthorizedImage(name)) { if (!isAnImage(name) && !isAuthorizedImage(name)) {
bootbox.alert(I18n.t('post.errors.upload_not_authorized', { authorized_extensions: authorizedImagesExtensions() })); bootbox.alert(I18n.t('post.errors.upload_not_authorized', { authorized_extensions: authorizedImagesExtensions() }));
return false; return false;
} }
} else if (opts["csvOnly"]) { } else if (opts.csvOnly) {
if (!(/\.csv$/i).test(name)) { if (!(/\.csv$/i).test(name)) {
bootbox.alert(I18n.t('user.invited.bulk_invite.error')); bootbox.alert(I18n.t('user.invited.bulk_invite.error'));
return false; return false;
@ -202,10 +210,10 @@ export function validateUploadedFile(file, opts) {
} }
} }
if (!opts["bypassNewUserRestriction"]) { if (!opts.bypassNewUserRestriction) {
// ensures that new users can upload a file // ensures that new users can upload a file
if (!Discourse.User.current().isAllowedToUploadAFile(opts["type"])) { if (!Discourse.User.current().isAllowedToUploadAFile(opts.type)) {
bootbox.alert(I18n.t(`post.errors.${opts["type"]}_upload_not_allowed_for_new_user`)); bootbox.alert(I18n.t(`post.errors.${opts.type}_upload_not_allowed_for_new_user`));
return false; return false;
} }
} }
@ -288,7 +296,9 @@ export function uploadLocation(url) {
export function getUploadMarkdown(upload) { export function getUploadMarkdown(upload) {
if (isAnImage(upload.original_filename)) { if (isAnImage(upload.original_filename)) {
return '<img src="' + upload.url + '" width="' + upload.width + '" height="' + upload.height + '">'; const split = upload.original_filename.split('.');
const name = split[split.length-2];
return `![${name}|${upload.width}x${upload.height}](${upload.url})`;
} else if (!Discourse.SiteSettings.prevent_anons_from_downloading_files && (/\.(mov|mp4|webm|ogv|mp3|ogg|wav|m4a)$/i).test(upload.original_filename)) { } else if (!Discourse.SiteSettings.prevent_anons_from_downloading_files && (/\.(mov|mp4|webm|ogv|mp3|ogg|wav|m4a)$/i).test(upload.original_filename)) {
return uploadLocation(upload.url); return uploadLocation(upload.url);
} else { } else {

View File

@ -106,7 +106,7 @@ export function mapRoutes() {
// can define admin routes. // can define admin routes.
Object.keys(requirejs._eak_seen).forEach(function(key) { Object.keys(requirejs._eak_seen).forEach(function(key) {
if (/route-map$/.test(key)) { if (/route-map$/.test(key)) {
var module = require(key, null, null, true); var module = requirejs(key, null, null, true);
if (!module || !module.default) { throw new Error(key + ' must export a route map.'); } if (!module || !module.default) { throw new Error(key + ' must export a route map.'); }
const mapObj = module.default; const mapObj = module.default;

View File

@ -0,0 +1,14 @@
// A mixin where hitting ESC calls `cancelled` and ctrl+enter calls `save.
export default {
keyDown(e) {
if (e.which === 27) {
this.sendAction('cancelled');
return false;
} else if (e.which === 13 && (e.ctrlKey || e.metaKey)) {
// CTRL+ENTER or CMD+ENTER
this.sendAction('save');
return false;
}
},
};

View File

@ -3,6 +3,11 @@ import { default as computed } from 'ember-addons/ember-computed-decorators';
export default Ember.Mixin.create({ export default Ember.Mixin.create({
@computed()
nameInstructions() {
return I18n.t(this.siteSettings.full_name_required ? 'user.name.instructions_required' : 'user.name.instructions');
},
// Validate the name. // Validate the name.
@computed('accountName') @computed('accountName')
nameValidation() { nameValidation() {

View File

@ -31,7 +31,7 @@ const Scrolling = Ember.Mixin.create({
opts = opts || { debounce: 100 }; opts = opts || { debounce: 100 };
// So we can not call the scrolled event while transitioning // So we can not call the scrolled event while transitioning
const router = Discourse.__container__.lookup('router:main').router; const router = Discourse.__container__.lookup('router:main')._routerMicrolib;
let onScrollMethod = () => { let onScrollMethod = () => {
if (router.activeTransition) { return; } if (router.activeTransition) { return; }

View File

@ -0,0 +1,35 @@
import InputValidation from 'discourse/models/input-validation';
import { on, default as computed } from 'ember-addons/ember-computed-decorators';
export default Ember.Mixin.create({
@on('init')
_createUserFields() {
if (!this.site) { return; }
let userFields = this.site.get('user_fields');
if (userFields) {
userFields = _.sortBy(userFields, 'position').map(function(f) {
return Ember.Object.create({ value: null, field: f });
});
}
this.set('userFields', userFields);
},
// Validate required fields
@computed('userFields.@each.value')
userFieldsValidation() {
let userFields = this.get('userFields');
if (userFields) { userFields = userFields.filterBy('field.required'); }
if (!Ember.isEmpty(userFields)) {
const anyEmpty = userFields.any(uf => {
const val = uf.get('value');
return !val || Ember.isEmpty(val);
});
if (anyEmpty) {
return InputValidation.create({ failed: true });
}
}
return InputValidation.create({ ok: true });
}
});

View File

@ -1,26 +0,0 @@
import Post from 'discourse/models/post';
export default Post.extend({
_attachCategory: function () {
const categoryId = this.get("category_id");
if (categoryId) {
this.set("category", Discourse.Category.findById(categoryId));
}
}.on("init"),
presentName: Ember.computed.or('name', 'username'),
sameUser: function() {
return this.get("username") === Discourse.User.currentProp("username");
}.property("username"),
descriptionKey: function () {
if (this.get("reply_to_post_number")) {
return this.get("sameUser") ? "you_replied_to_post" : "user_replied_to_post";
} else {
return this.get("sameUser") ? "you_replied_to_topic" : "user_replied_to_topic";
}
}.property("reply_to_post_number", "sameUser")
});

View File

@ -2,7 +2,6 @@ import { ajax } from 'discourse/lib/ajax';
import { default as computed, observes } from "ember-addons/ember-computed-decorators"; import { default as computed, observes } from "ember-addons/ember-computed-decorators";
import GroupHistory from 'discourse/models/group-history'; import GroupHistory from 'discourse/models/group-history';
import RestModel from 'discourse/models/rest'; import RestModel from 'discourse/models/rest';
import { popupAjaxError } from 'discourse/lib/ajax-error';
const Group = RestModel.extend({ const Group = RestModel.extend({
limit: 50, limit: 50,
@ -114,23 +113,27 @@ const Group = RestModel.extend({
return aliasLevel === '99'; return aliasLevel === '99';
}, },
@observes("visible", "canEveryoneMention") @observes("visibility_level", "canEveryoneMention")
_updateAllowMembershipRequests() { _updateAllowMembershipRequests() {
if (!this.get('visible') || !this.get('canEveryoneMention')) { if (this.get('visibility_level') !== 0 || !this.get('canEveryoneMention')) {
this.set ('allow_membership_requests', false); this.set ('allow_membership_requests', false);
} }
}, },
@observes("visible") @observes("visibility_level")
_updatePublic() { _updatePublic() {
if (!this.get('visible')) this.set('public', false); let visibility_level = parseInt(this.get('visibility_level'));
if (visibility_level !== 0) {
this.set('public', false);
this.set('allow_membership_requests', false);
}
}, },
asJSON() { asJSON() {
return { return {
name: this.get('name'), name: this.get('name'),
alias_level: this.get('alias_level'), alias_level: this.get('alias_level'),
visible: !!this.get('visible'), visibility_level: this.get('visibility_level'),
automatic_membership_email_domains: this.get('emailDomains'), automatic_membership_email_domains: this.get('emailDomains'),
automatic_membership_retroactive: !!this.get('automatic_membership_retroactive'), automatic_membership_retroactive: !!this.get('automatic_membership_retroactive'),
title: this.get('title'), title: this.get('title'),
@ -202,12 +205,18 @@ const Group = RestModel.extend({
data: { notification_level, user_id: userId }, data: { notification_level, user_id: userId },
type: "POST" type: "POST"
}); });
} },
requestMembership() {
return ajax(`/groups/${this.get('name')}/request_membership`, {
type: "POST"
});
},
}); });
Group.reopenClass({ Group.reopenClass({
findAll(opts) { findAll(opts) {
return ajax("/admin/groups.json", { data: opts }).then(function (groups){ return ajax("/groups/search.json", { data: opts }).then(groups => {
return groups.map(g => Group.create(g)); return groups.map(g => Group.create(g));
}); });
}, },
@ -216,10 +225,6 @@ Group.reopenClass({
return ajax("/groups/" + name + ".json").then(result => Group.create(result.basic_group)); return ajax("/groups/" + name + ".json").then(result => Group.create(result.basic_group));
}, },
loadOwners(name) {
return ajax('/groups/' + name + '/owners.json').catch(popupAjaxError);
},
loadMembers(name, offset, limit, params) { loadMembers(name, offset, limit, params) {
return ajax('/groups/' + name + '/members.json', { return ajax('/groups/' + name + '/members.json', {
data: _.extend({ data: _.extend({

View File

@ -58,6 +58,10 @@ Invite.reopenClass({
reinviteAll() { reinviteAll() {
return ajax('/invites/reinvite-all', { type: 'POST' }); return ajax('/invites/reinvite-all', { type: 'POST' });
},
rescindAll() {
return ajax('/invites/rescind-all', { type: 'POST' });
} }
}); });

View File

@ -8,6 +8,7 @@ import computed from 'ember-addons/ember-computed-decorators';
import { postUrl } from 'discourse/lib/utilities'; import { postUrl } from 'discourse/lib/utilities';
import { cook } from 'discourse/lib/text'; import { cook } from 'discourse/lib/text';
import { userPath } from 'discourse/lib/url'; import { userPath } from 'discourse/lib/url';
import Composer from 'discourse/models/composer';
const Post = RestModel.extend({ const Post = RestModel.extend({
@ -104,7 +105,6 @@ const Post = RestModel.extend({
createProperties() { createProperties() {
// composer only used once, defer the dependency // composer only used once, defer the dependency
const Composer = require('discourse/models/composer').default;
const data = this.getProperties(Composer.serializedFieldsForCreate()); const data = this.getProperties(Composer.serializedFieldsForCreate());
data.reply_to_post_number = this.get('reply_to_post_number'); data.reply_to_post_number = this.get('reply_to_post_number');
data.image_sizes = this.get('imageSizes'); data.image_sizes = this.get('imageSizes');

View File

@ -3,7 +3,7 @@ const RestModel = Ember.Object.extend({
isCreated: Ember.computed.equal('__state', 'created'), isCreated: Ember.computed.equal('__state', 'created'),
isSaving: false, isSaving: false,
afterUpdate: Ember.K, afterUpdate() { },
update(props) { update(props) {
if (this.get('isSaving')) { return Ember.RSVP.reject(); } if (this.get('isSaving')) { return Ember.RSVP.reject(); }

View File

@ -297,7 +297,16 @@ export default Ember.Object.extend({
if (existing) { if (existing) {
delete obj.id; delete obj.id;
const klass = this.register.lookupFactory('model:' + type) || RestModel; let klass = this.register.lookupFactory('model:' + type);
if (klass && klass.class) {
klass = klass.class;
}
if (!klass) {
klass = RestModel;
}
existing.setProperties(klass.munge(obj)); existing.setProperties(klass.munge(obj));
obj.id = id; obj.id = id;
return existing; return existing;

View File

@ -1,6 +1,6 @@
import { ajax } from 'discourse/lib/ajax'; import { ajax } from 'discourse/lib/ajax';
import { url } from 'discourse/lib/computed'; import { url } from 'discourse/lib/computed';
import AdminPost from 'discourse/models/admin-post'; import UserAction from 'discourse/models/user-action';
export default Discourse.Model.extend({ export default Discourse.Model.extend({
loaded: false, loaded: false,
@ -36,7 +36,7 @@ export default Discourse.Model.extend({
return ajax(this.get("url"), { cache: false }).then(function (result) { return ajax(this.get("url"), { cache: false }).then(function (result) {
if (result) { if (result) {
const posts = result.map(function (post) { return AdminPost.create(post); }); const posts = result.map(function (post) { return UserAction.create(post); });
self.get("content").pushObjects(posts); self.get("content").pushObjects(posts);
self.setProperties({ self.setProperties({
loaded: true, loaded: true,

Some files were not shown because too many files have changed in this diff Show More