mirror of
https://github.com/discourse/discourse.git
synced 2025-03-09 14:34:35 +00:00
Merge branch 'master' into search_posts_with_images
This commit is contained in:
commit
ea0e90b2b0
@ -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/
|
||||||
|
15
.eslintrc
15
.eslintrc
@ -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,
|
||||||
|
@ -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
|
|
10
.travis.yml
10
.travis.yml
@ -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"
|
||||||
|
@ -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
13
Gemfile
@ -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
|
||||||
|
172
Gemfile.lock
172
Gemfile.lock
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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: {
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -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
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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']);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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}}
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
|
||||||
})();
|
})();
|
||||||
|
@ -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) {
|
||||||
|
@ -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;
|
||||||
|
@ -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) {
|
||||||
|
@ -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({
|
||||||
|
@ -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 {
|
||||||
|
@ -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));
|
||||||
|
@ -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 {
|
||||||
|
@ -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) {
|
||||||
|
@ -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);
|
||||||
},
|
},
|
||||||
|
@ -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();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -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";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
15
app/assets/javascripts/discourse/components/cook-text.js.es6
Normal file
15
app/assets/javascripts/discourse/components/cook-text.js.es6
Normal 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;
|
@ -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}:`)
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -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,
|
||||||
|
|
||||||
|
543
app/assets/javascripts/discourse/components/emoji-picker.js.es6
Normal file
543
app/assets/javascripts/discourse/components/emoji-picker.js.es6
Normal 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 don’t 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)}")`);
|
||||||
|
},
|
||||||
|
});
|
@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
});
|
@ -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();
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
export default Ember.Component.extend({
|
|
||||||
actions: {
|
|
||||||
// TODO: When on Ember 1.13, use a closure action
|
|
||||||
loadMore() {
|
|
||||||
this.sendAction('loadMore');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
@ -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;});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -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')
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
@ -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});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
@ -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');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -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'));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -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;
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
import KeyEnterEscape from 'discourse/mixins/key-enter-escape';
|
||||||
|
|
||||||
|
export default Ember.Component.extend(KeyEnterEscape, {
|
||||||
|
elementId: 'topic-title',
|
||||||
|
});
|
@ -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; }
|
||||||
|
|
||||||
|
@ -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) {
|
||||||
|
@ -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')
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -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 });
|
||||||
|
@ -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');
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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));
|
||||||
|
@ -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; }
|
||||||
|
@ -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) {
|
||||||
|
@ -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');
|
||||||
|
@ -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({
|
|||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@ -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');
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -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 = {
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -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}));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
import { cook } from 'discourse/lib/text';
|
|
||||||
import { registerUnbound } from 'discourse-common/lib/helpers';
|
|
||||||
|
|
||||||
registerUnbound('cook-text', cook);
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -2,5 +2,5 @@
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "inject-objects",
|
name: "inject-objects",
|
||||||
initialize: Ember.K
|
initialize() { }
|
||||||
};
|
};
|
||||||
|
@ -2,5 +2,5 @@
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "register-discourse-location",
|
name: "register-discourse-location",
|
||||||
initialize: Ember.K
|
initialize() { }
|
||||||
};
|
};
|
||||||
|
@ -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);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
32
app/assets/javascripts/discourse/lib/dirty-keys.js.es6
Normal file
32
app/assets/javascripts/discourse/lib/dirty-keys.js.es6
Normal 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
@ -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 };
|
|
@ -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});
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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) {
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -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() {
|
||||||
|
@ -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('#');
|
||||||
|
@ -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 ``;
|
||||||
} 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 {
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -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() {
|
||||||
|
@ -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; }
|
||||||
|
@ -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 });
|
||||||
|
}
|
||||||
|
});
|
@ -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")
|
|
||||||
|
|
||||||
});
|
|
@ -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({
|
||||||
|
@ -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' });
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -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');
|
||||||
|
@ -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(); }
|
||||||
|
@ -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;
|
||||||
|
@ -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
Loading…
x
Reference in New Issue
Block a user