Merge master
This commit is contained in:
commit
44a0ff7688
4
.gitignore
vendored
4
.gitignore
vendored
@ -10,6 +10,8 @@ dump.rdb
|
||||
|
||||
bin/*
|
||||
|
||||
data/
|
||||
|
||||
.sass-cache/*
|
||||
public/csv/*
|
||||
public/plugins/*
|
||||
@ -40,6 +42,7 @@ config/discourse.conf
|
||||
/tmp
|
||||
/logfile
|
||||
log/
|
||||
bootsnap-load-path-cache
|
||||
|
||||
# Ignore plugins except for the bundled ones.
|
||||
/plugins/*
|
||||
@ -48,6 +51,7 @@ log/
|
||||
!/plugins/poll/
|
||||
!/plugins/discourse-details/
|
||||
!/plugins/discourse-nginx-performance-report
|
||||
!/plugins/discourse-narrative-bot
|
||||
/plugins/*/auto_generated/
|
||||
|
||||
/spec/fixtures/plugins/my_plugin/auto_generated
|
||||
|
||||
@ -2,10 +2,11 @@ skip_missing_workers: true
|
||||
allow_lossy: false
|
||||
# PNG
|
||||
advpng: false
|
||||
optipng:
|
||||
optipng:
|
||||
level: 2
|
||||
pngcrush: false
|
||||
pngout: false
|
||||
pngquant: false
|
||||
# JPG
|
||||
jpegrecompress: false
|
||||
timeout: 15
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
{
|
||||
"maxReviewers": 2,
|
||||
"message": "Thanks @pullRequester for your pull request :+1:. By analyzing the blame information on this pull request, I identified @reviewers to be potential reviewers.",
|
||||
"requiredOrgs": ["discourse"],
|
||||
"skipCollaboratorPR": false,
|
||||
"delayed": false,
|
||||
"delayedUntil": "1d"
|
||||
}
|
||||
14
.rubocop.yml
Normal file
14
.rubocop.yml
Normal file
@ -0,0 +1,14 @@
|
||||
AllCops:
|
||||
TargetRubyVersion: 2.3
|
||||
|
||||
Metrics/LineLength:
|
||||
Max: 120
|
||||
|
||||
Metrics/MethodLength:
|
||||
Enabled: false
|
||||
|
||||
Style/Documentation:
|
||||
Enabled: false
|
||||
|
||||
Style/FrozenStringLiteralComment:
|
||||
Enabled: False
|
||||
@ -1 +1 @@
|
||||
2.3.0
|
||||
2.4.1
|
||||
|
||||
17
.travis.yml
17
.travis.yml
@ -5,8 +5,10 @@ env:
|
||||
- DISCOURSE_HOSTNAME=www.example.com
|
||||
- RUBY_GC_MALLOC_LIMIT=50000000
|
||||
matrix:
|
||||
- "RAILS_MASTER=0"
|
||||
- "RAILS_MASTER=1"
|
||||
- "RAILS_MASTER=0 QUNIT_RUN=0"
|
||||
- "RAILS_MASTER=1 QUNIT_RUN=0"
|
||||
- "RAILS_MASTER=0 QUNIT_RUN=1"
|
||||
- "RAILS_MASTER=1 QUNIT_RUN=1"
|
||||
|
||||
addons:
|
||||
postgresql: 9.5
|
||||
@ -19,12 +21,13 @@ addons:
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- env: "RAILS_MASTER=1"
|
||||
- rvm: rbx-2
|
||||
- env: "RAILS_MASTER=1 QUNIT_RUN=0"
|
||||
- env: "RAILS_MASTER=1 QUNIT_RUN=1"
|
||||
fast_finish: true
|
||||
|
||||
rvm:
|
||||
- 2.3.1
|
||||
- 2.4.1
|
||||
- 2.3.3
|
||||
|
||||
services:
|
||||
- redis-server
|
||||
@ -42,6 +45,7 @@ before_install:
|
||||
- git clone --depth=1 https://github.com/discourse/discourse-spoiler-alert.git plugins/discourse-spoiler-alert
|
||||
- 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-slack-official.git plugins/discourse-slack-official
|
||||
- npm i -g eslint babel-eslint
|
||||
- eslint app/assets/javascripts
|
||||
- eslint --ext .es6 app/assets/javascripts
|
||||
@ -56,4 +60,5 @@ install:
|
||||
- bash -c "if [ '$RAILS_MASTER' == '1' ]; then bundle update --retry=3 --jobs=3 arel rails seed-fu; fi"
|
||||
- bash -c "if [ '$RAILS_MASTER' == '0' ]; then bundle install --without development --deployment --retry=3 --jobs=3; fi"
|
||||
|
||||
script: "bundle exec rspec && bundle exec rake plugin:spec && bundle exec rake qunit:test['200000']"
|
||||
script:
|
||||
- bash -c "if [ '$QUNIT_RUN' == '0' ]; then bundle exec rspec && bundle exec rake plugin:spec; else bundle exec rake qunit:test['200000']; fi"
|
||||
|
||||
14
.tx/config
14
.tx/config
@ -1,6 +1,6 @@
|
||||
[main]
|
||||
host = https://www.transifex.com
|
||||
lang_map = es_ES: es, fr_FR: fr, ko_KR: ko, pt_PT: pt, sk_SK: sk, vi_VN: vi
|
||||
lang_map = el_GR: el, es_ES: es, fr_FR: fr, ko_KR: ko, pt_PT: pt, sk_SK: sk, vi_VN: vi
|
||||
|
||||
[discourse-org.clientenyml]
|
||||
file_filter = config/locales/client.<lang>.yml
|
||||
@ -32,6 +32,18 @@ source_file = vendor/gems/discourse_imgur/lib/discourse_imgur/locale/server.en.y
|
||||
source_lang = en
|
||||
type = YML
|
||||
|
||||
[discourse-org.narrativeclientenyml]
|
||||
file_filter = plugins/discourse-narrative-bot/config/locales/client.<lang>.yml
|
||||
source_file = plugins/discourse-narrative-bot/config/locales/client.en.yml
|
||||
source_lang = en
|
||||
type = YML
|
||||
|
||||
[discourse-org.narrativeserverenyml]
|
||||
file_filter = plugins/discourse-narrative-bot/config/locales/server.<lang>.yml
|
||||
source_file = plugins/discourse-narrative-bot/config/locales/server.en.yml
|
||||
source_lang = en
|
||||
type = YML
|
||||
|
||||
[discourse-org.403html]
|
||||
file_filter = public/403.<lang>.html
|
||||
source_file = public/403.html
|
||||
|
||||
37
Gemfile
37
Gemfile
@ -2,6 +2,9 @@ source 'https://rubygems.org'
|
||||
# if there is a super emergency and rubygems is playing up, try
|
||||
#source 'http://production.cf.rubygems.org'
|
||||
|
||||
# does not install in linux ATM, so hack this for now
|
||||
gem 'bootsnap', require: false
|
||||
|
||||
def rails_master?
|
||||
ENV["RAILS_MASTER"] == '1'
|
||||
end
|
||||
@ -58,18 +61,16 @@ gem 'fast_xs'
|
||||
|
||||
gem 'fast_xor'
|
||||
|
||||
# while we sort out https://github.com/sdsykes/fastimage/pull/46
|
||||
gem 'discourse_fastimage', '2.0.3', require: 'fastimage'
|
||||
gem 'fastimage', '2.1.0'
|
||||
gem 'aws-sdk', require: false
|
||||
gem 'excon', require: false
|
||||
gem 'unf', require: false
|
||||
|
||||
gem 'email_reply_trimmer', '0.1.6'
|
||||
|
||||
# note: for image_optim to correctly work you need to follow
|
||||
# https://github.com/toy/image_optim
|
||||
# pinned due to https://github.com/toy/image_optim/pull/75, docker image must be upgraded to upgrade
|
||||
gem 'image_optim', '0.20.2'
|
||||
# TODO Use official image_optim gem once https://github.com/toy/image_optim/pull/149
|
||||
# is merged.
|
||||
gem 'discourse_image_optim', require: 'image_optim'
|
||||
gem 'multi_json'
|
||||
gem 'mustache'
|
||||
gem 'nokogiri'
|
||||
@ -92,20 +93,16 @@ gem 'pry-rails', require: false
|
||||
gem 'r2', '~> 0.2.5', require: false
|
||||
gem 'rake'
|
||||
|
||||
|
||||
gem 'thor', require: false
|
||||
gem 'rest-client'
|
||||
gem 'rinku'
|
||||
gem 'sanitize'
|
||||
gem 'sass'
|
||||
gem 'sass-rails'
|
||||
gem 'sidekiq'
|
||||
gem 'sidekiq-statistic'
|
||||
|
||||
# for sidekiq web
|
||||
gem 'sinatra', require: false
|
||||
gem 'execjs', require: false
|
||||
gem 'mini_racer'
|
||||
gem 'thin', require: false
|
||||
gem 'highline', require: false
|
||||
gem 'rack-protection' # security
|
||||
|
||||
@ -118,15 +115,18 @@ group :assets do
|
||||
end
|
||||
|
||||
group :test do
|
||||
gem 'webmock', require: false
|
||||
gem 'fakeweb', '~> 1.3.0', require: false
|
||||
gem 'minitest', require: false
|
||||
gem 'timecop'
|
||||
# TODO: Remove once we upgrade to Rails 5.
|
||||
gem 'test_after_commit'
|
||||
end
|
||||
|
||||
group :test, :development do
|
||||
gem 'rspec'
|
||||
gem 'mock_redis'
|
||||
gem 'listen', '0.7.3', require: false
|
||||
gem 'listen', require: false
|
||||
gem 'certified', require: false
|
||||
# later appears to break Fabricate(:topic, category: category)
|
||||
gem 'fabrication', '2.9.8', require: false
|
||||
@ -184,10 +184,11 @@ gem 'rmmseg-cpp', require: false
|
||||
|
||||
gem 'logster'
|
||||
|
||||
# perftools only works on 1.9 atm
|
||||
group :profile do
|
||||
# travis refuses to install this, instead of fuffing, just avoid it for now
|
||||
#
|
||||
# if you need to profile, uncomment out this line
|
||||
# gem 'rack-perftools_profiler', require: 'rack/perftools_profiler', platform: :mri_19
|
||||
gem 'sassc', require: false
|
||||
|
||||
|
||||
if ENV["IMPORT"] == "1"
|
||||
gem 'mysql2'
|
||||
gem 'redcarpet'
|
||||
gem 'sqlite3', '~> 1.3.13'
|
||||
end
|
||||
|
||||
225
Gemfile.lock
225
Gemfile.lock
@ -1,47 +1,48 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actionmailer (4.2.7.1)
|
||||
actionpack (= 4.2.7.1)
|
||||
actionview (= 4.2.7.1)
|
||||
activejob (= 4.2.7.1)
|
||||
actionmailer (4.2.8)
|
||||
actionpack (= 4.2.8)
|
||||
actionview (= 4.2.8)
|
||||
activejob (= 4.2.8)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 1.0, >= 1.0.5)
|
||||
actionpack (4.2.7.1)
|
||||
actionview (= 4.2.7.1)
|
||||
activesupport (= 4.2.7.1)
|
||||
actionpack (4.2.8)
|
||||
actionview (= 4.2.8)
|
||||
activesupport (= 4.2.8)
|
||||
rack (~> 1.6)
|
||||
rack-test (~> 0.6.2)
|
||||
rails-dom-testing (~> 1.0, >= 1.0.5)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||
actionview (4.2.7.1)
|
||||
activesupport (= 4.2.7.1)
|
||||
actionview (4.2.8)
|
||||
activesupport (= 4.2.8)
|
||||
builder (~> 3.1)
|
||||
erubis (~> 2.7.0)
|
||||
rails-dom-testing (~> 1.0, >= 1.0.5)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
||||
active_model_serializers (0.8.3)
|
||||
activemodel (>= 3.0)
|
||||
activejob (4.2.7.1)
|
||||
activesupport (= 4.2.7.1)
|
||||
activejob (4.2.8)
|
||||
activesupport (= 4.2.8)
|
||||
globalid (>= 0.3.0)
|
||||
activemodel (4.2.7.1)
|
||||
activesupport (= 4.2.7.1)
|
||||
activemodel (4.2.8)
|
||||
activesupport (= 4.2.8)
|
||||
builder (~> 3.1)
|
||||
activerecord (4.2.7.1)
|
||||
activemodel (= 4.2.7.1)
|
||||
activesupport (= 4.2.7.1)
|
||||
activerecord (4.2.8)
|
||||
activemodel (= 4.2.8)
|
||||
activesupport (= 4.2.8)
|
||||
arel (~> 6.0)
|
||||
activesupport (4.2.7.1)
|
||||
activesupport (4.2.8)
|
||||
i18n (~> 0.7)
|
||||
json (~> 1.7, >= 1.7.7)
|
||||
minitest (~> 5.1)
|
||||
thread_safe (~> 0.3, >= 0.3.4)
|
||||
tzinfo (~> 1.1)
|
||||
addressable (2.5.1)
|
||||
public_suffix (~> 2.0, >= 2.0.2)
|
||||
annotate (2.7.1)
|
||||
activerecord (>= 3.2, < 6.0)
|
||||
rake (>= 10.4, < 12.0)
|
||||
arel (6.0.3)
|
||||
arel (6.0.4)
|
||||
aws-sdk (2.5.3)
|
||||
aws-sdk-resources (= 2.5.3)
|
||||
aws-sdk-core (2.5.3)
|
||||
@ -61,22 +62,30 @@ GEM
|
||||
rack (>= 0.9.0)
|
||||
binding_of_caller (0.7.2)
|
||||
debug_inspector (>= 0.0.1)
|
||||
builder (3.2.2)
|
||||
bootsnap (0.3.0)
|
||||
msgpack (~> 1.0)
|
||||
builder (3.2.3)
|
||||
bullet (5.4.2)
|
||||
activesupport (>= 3.0.0)
|
||||
uniform_notifier (~> 1.10.0)
|
||||
byebug (9.0.6)
|
||||
certified (1.0.0)
|
||||
coderay (1.1.1)
|
||||
concurrent-ruby (1.0.2)
|
||||
concurrent-ruby (1.0.5)
|
||||
connection_pool (2.2.0)
|
||||
crack (0.4.3)
|
||||
safe_yaml (~> 1.0.0)
|
||||
crass (1.0.2)
|
||||
daemons (1.2.4)
|
||||
debug_inspector (0.0.2)
|
||||
diff-lcs (1.2.5)
|
||||
diff-lcs (1.3)
|
||||
discourse-qunit-rails (0.0.9)
|
||||
railties
|
||||
discourse_fastimage (2.0.3)
|
||||
discourse_image_optim (0.24.5)
|
||||
exifr (~> 1.2, >= 1.2.2)
|
||||
fspath (~> 3.0)
|
||||
image_size (~> 1.5)
|
||||
in_threads (~> 1.3)
|
||||
progress (~> 3.0, >= 3.0.1)
|
||||
domain_name (0.5.25)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
email_reply_trimmer (0.1.6)
|
||||
@ -94,10 +103,9 @@ GEM
|
||||
railties (>= 3.1)
|
||||
ember-source (2.10.0)
|
||||
erubis (2.7.0)
|
||||
eventmachine (1.2.0.1)
|
||||
excon (0.53.0)
|
||||
excon (0.55.0)
|
||||
execjs (2.7.0)
|
||||
exifr (1.2.4)
|
||||
exifr (1.2.5)
|
||||
fabrication (2.9.8)
|
||||
fakeweb (1.3.0)
|
||||
faraday (0.11.0)
|
||||
@ -107,15 +115,17 @@ GEM
|
||||
rake
|
||||
rake-compiler
|
||||
fast_xs (0.8.0)
|
||||
ffi (1.9.10)
|
||||
fastimage (2.1.0)
|
||||
ffi (1.9.18)
|
||||
flamegraph (0.9.5)
|
||||
foreman (0.82.0)
|
||||
thor (~> 0.19.1)
|
||||
fspath (2.1.1)
|
||||
fspath (3.1.0)
|
||||
gc_tracer (1.5.1)
|
||||
globalid (0.3.7)
|
||||
activesupport (>= 4.1.0)
|
||||
guess_html_encoding (0.0.11)
|
||||
hashdiff (0.3.4)
|
||||
hashie (3.5.5)
|
||||
highline (1.7.8)
|
||||
hiredis (0.6.1)
|
||||
@ -123,63 +133,59 @@ GEM
|
||||
http-cookie (1.0.2)
|
||||
domain_name (~> 0.5)
|
||||
http_accept_language (2.0.5)
|
||||
i18n (0.7.0)
|
||||
image_optim (0.20.2)
|
||||
exifr (~> 1.1, >= 1.1.3)
|
||||
fspath (~> 2.1)
|
||||
image_size (~> 1.3)
|
||||
in_threads (~> 1.3)
|
||||
progress (~> 3.0, >= 3.0.1)
|
||||
image_size (1.4.1)
|
||||
in_threads (1.3.1)
|
||||
i18n (0.8.1)
|
||||
image_size (1.5.0)
|
||||
in_threads (1.4.0)
|
||||
jmespath (1.3.1)
|
||||
jquery-rails (4.2.1)
|
||||
rails-dom-testing (>= 1, < 3)
|
||||
railties (>= 4.2.0)
|
||||
thor (>= 0.14, < 2.0)
|
||||
json (1.8.3)
|
||||
jwt (1.5.6)
|
||||
kgio (2.10.0)
|
||||
libv8 (5.3.332.38.3)
|
||||
listen (0.7.3)
|
||||
logster (1.2.5)
|
||||
kgio (2.11.0)
|
||||
libv8 (5.3.332.38.5)
|
||||
listen (3.1.5)
|
||||
rb-fsevent (~> 0.9, >= 0.9.4)
|
||||
rb-inotify (~> 0.9, >= 0.9.7)
|
||||
ruby_dep (~> 1.2)
|
||||
logster (1.2.7)
|
||||
loofah (2.0.3)
|
||||
nokogiri (>= 1.5.9)
|
||||
lru_redux (1.1.0)
|
||||
mail (2.6.4)
|
||||
mail (2.6.5)
|
||||
mime-types (>= 1.16, < 4)
|
||||
memory_profiler (0.9.7)
|
||||
message_bus (2.0.2)
|
||||
rack (>= 1.1.3)
|
||||
metaclass (0.0.4)
|
||||
method_source (0.8.2)
|
||||
mime-types (2.99.2)
|
||||
mime-types (2.99.3)
|
||||
mini_portile2 (2.1.0)
|
||||
mini_racer (0.1.7)
|
||||
mini_racer (0.1.9)
|
||||
libv8 (~> 5.3)
|
||||
minitest (5.9.1)
|
||||
minitest (5.10.1)
|
||||
mocha (1.1.0)
|
||||
metaclass (~> 0.0.1)
|
||||
mock_redis (0.15.4)
|
||||
moneta (0.8.1)
|
||||
msgpack (0.7.6)
|
||||
moneta (1.0.0)
|
||||
msgpack (1.1.0)
|
||||
multi_json (1.12.1)
|
||||
multi_xml (0.6.0)
|
||||
multipart-post (2.0.0)
|
||||
mustache (1.0.3)
|
||||
mustache (1.0.5)
|
||||
netrc (0.11.0)
|
||||
nokogiri (1.6.8.1)
|
||||
nokogiri (1.7.2)
|
||||
mini_portile2 (~> 2.1.0)
|
||||
nokogumbo (1.4.7)
|
||||
nokogumbo (1.4.10)
|
||||
nokogiri
|
||||
oauth (0.4.7)
|
||||
oauth (0.5.1)
|
||||
oauth2 (1.3.1)
|
||||
faraday (>= 0.8, < 0.12)
|
||||
jwt (~> 1.0)
|
||||
multi_json (~> 1.3)
|
||||
multi_xml (~> 0.5)
|
||||
rack (>= 1.2, < 3)
|
||||
oj (2.17.5)
|
||||
oj (3.0.5)
|
||||
omniauth (1.6.1)
|
||||
hashie (>= 3.4.6, < 3.6.0)
|
||||
rack (>= 1.6.2, < 3)
|
||||
@ -205,23 +211,22 @@ GEM
|
||||
omniauth-openid (1.0.1)
|
||||
omniauth (~> 1.0)
|
||||
rack-openid (~> 1.3.1)
|
||||
omniauth-twitter (1.2.1)
|
||||
json (~> 1.3)
|
||||
omniauth-twitter (1.3.0)
|
||||
omniauth-oauth (~> 1.1)
|
||||
rack
|
||||
onebox (1.7.7)
|
||||
onebox (1.8.8)
|
||||
fast_blank (>= 1.0.0)
|
||||
htmlentities (~> 4.3.4)
|
||||
moneta (~> 0.8)
|
||||
htmlentities (~> 4.3)
|
||||
moneta (~> 1.0)
|
||||
multi_json (~> 1.11)
|
||||
mustache
|
||||
nokogiri (~> 1.6.6)
|
||||
nokogiri (~> 1.7)
|
||||
sanitize
|
||||
openid-redis-store (0.0.2)
|
||||
redis
|
||||
ruby-openid
|
||||
pg (0.19.0)
|
||||
progress (3.1.1)
|
||||
progress (3.3.1)
|
||||
pry (0.10.4)
|
||||
coderay (~> 1.1.0)
|
||||
method_source (~> 0.8.1)
|
||||
@ -230,10 +235,11 @@ GEM
|
||||
pry (>= 0.9.10, < 0.11.0)
|
||||
pry-rails (0.3.4)
|
||||
pry (>= 0.9.10)
|
||||
public_suffix (2.0.5)
|
||||
puma (3.6.0)
|
||||
r2 (0.2.6)
|
||||
rack (1.6.5)
|
||||
rack-mini-profiler (0.10.1)
|
||||
rack (1.6.8)
|
||||
rack-mini-profiler (0.10.4)
|
||||
rack (>= 1.2.0)
|
||||
rack-openid (1.3.1)
|
||||
rack (>= 1.1.0)
|
||||
@ -242,34 +248,34 @@ GEM
|
||||
rack
|
||||
rack-test (0.6.3)
|
||||
rack (>= 1.0)
|
||||
rails (4.2.7.1)
|
||||
actionmailer (= 4.2.7.1)
|
||||
actionpack (= 4.2.7.1)
|
||||
actionview (= 4.2.7.1)
|
||||
activejob (= 4.2.7.1)
|
||||
activemodel (= 4.2.7.1)
|
||||
activerecord (= 4.2.7.1)
|
||||
activesupport (= 4.2.7.1)
|
||||
rails (4.2.8)
|
||||
actionmailer (= 4.2.8)
|
||||
actionpack (= 4.2.8)
|
||||
actionview (= 4.2.8)
|
||||
activejob (= 4.2.8)
|
||||
activemodel (= 4.2.8)
|
||||
activerecord (= 4.2.8)
|
||||
activesupport (= 4.2.8)
|
||||
bundler (>= 1.3.0, < 2.0)
|
||||
railties (= 4.2.7.1)
|
||||
railties (= 4.2.8)
|
||||
sprockets-rails
|
||||
rails-deprecated_sanitizer (1.0.3)
|
||||
activesupport (>= 4.2.0.alpha)
|
||||
rails-dom-testing (1.0.7)
|
||||
rails-dom-testing (1.0.8)
|
||||
activesupport (>= 4.2.0.beta, < 5.0)
|
||||
nokogiri (~> 1.6.0)
|
||||
nokogiri (~> 1.6)
|
||||
rails-deprecated_sanitizer (>= 1.0.1)
|
||||
rails-html-sanitizer (1.0.3)
|
||||
loofah (~> 2.0)
|
||||
rails_multisite (1.0.6)
|
||||
rails (> 4.2, < 5)
|
||||
railties (4.2.7.1)
|
||||
actionpack (= 4.2.7.1)
|
||||
activesupport (= 4.2.7.1)
|
||||
railties (4.2.8)
|
||||
actionpack (= 4.2.8)
|
||||
activesupport (= 4.2.8)
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.18.1, < 2.0)
|
||||
raindrops (0.17.0)
|
||||
rake (11.2.2)
|
||||
raindrops (0.18.0)
|
||||
rake (11.3.0)
|
||||
rake-compiler (0.9.9)
|
||||
rake
|
||||
rb-fsevent (0.9.7)
|
||||
@ -279,7 +285,7 @@ GEM
|
||||
ffi (>= 1.0.6)
|
||||
msgpack (>= 0.4.3)
|
||||
trollop (>= 1.16.2)
|
||||
redis (3.3.1)
|
||||
redis (3.3.3)
|
||||
redis-namespace (1.5.2)
|
||||
redis (~> 3.0, >= 3.0.4)
|
||||
rest-client (1.8.0)
|
||||
@ -317,17 +323,17 @@ GEM
|
||||
ruby-readability (0.7.0)
|
||||
guess_html_encoding (>= 0.0.4)
|
||||
nokogiri (>= 1.6.0)
|
||||
sanitize (4.0.1)
|
||||
ruby_dep (1.5.0)
|
||||
safe_yaml (1.0.4)
|
||||
sanitize (4.4.0)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.4.4)
|
||||
nokogumbo (~> 1.4.1)
|
||||
sass (3.2.19)
|
||||
sass-rails (5.0.4)
|
||||
railties (>= 4.0.0, < 5.0)
|
||||
sass (~> 3.1)
|
||||
sprockets (>= 2.8, < 4.0)
|
||||
sprockets-rails (>= 2.0, < 4.0)
|
||||
tilt (>= 1.1, < 3)
|
||||
sass (3.4.23)
|
||||
sassc (1.11.2)
|
||||
bundler
|
||||
ffi (~> 1.9.6)
|
||||
sass (>= 3.3.0)
|
||||
seed-fu (2.3.5)
|
||||
activerecord (>= 3.1, < 4.3)
|
||||
activesupport (>= 3.1, < 4.3)
|
||||
@ -342,8 +348,6 @@ GEM
|
||||
connection_pool (~> 2.2, >= 2.2.0)
|
||||
rack-protection (>= 1.5.0)
|
||||
redis (~> 3.2, >= 3.2.1)
|
||||
sidekiq-statistic (1.2.0)
|
||||
sidekiq (>= 3.3.4, < 5)
|
||||
simple-rss (1.3.1)
|
||||
sinatra (1.4.6)
|
||||
rack (~> 1.4)
|
||||
@ -354,34 +358,36 @@ GEM
|
||||
spork-rails (4.0.0)
|
||||
rails (>= 3.0.0, < 5)
|
||||
spork (>= 1.0rc0)
|
||||
sprockets (3.6.3)
|
||||
sprockets (3.7.1)
|
||||
concurrent-ruby (~> 1.0)
|
||||
rack (> 1, < 3)
|
||||
sprockets-rails (3.1.1)
|
||||
sprockets-rails (3.2.0)
|
||||
actionpack (>= 4.0)
|
||||
activesupport (>= 4.0)
|
||||
sprockets (>= 3.0.0)
|
||||
stackprof (0.2.10)
|
||||
thin (1.7.0)
|
||||
daemons (~> 1.0, >= 1.0.9)
|
||||
eventmachine (~> 1.0, >= 1.0.4)
|
||||
rack (>= 1, < 3)
|
||||
thor (0.19.1)
|
||||
thread_safe (0.3.5)
|
||||
test_after_commit (1.1.0)
|
||||
activerecord (>= 3.2)
|
||||
thor (0.19.4)
|
||||
thread_safe (0.3.6)
|
||||
tilt (2.0.5)
|
||||
timecop (0.8.1)
|
||||
trollop (2.1.2)
|
||||
tzinfo (1.2.2)
|
||||
tzinfo (1.2.3)
|
||||
thread_safe (~> 0.1)
|
||||
uglifier (3.0.2)
|
||||
execjs (>= 0.3.0, < 3)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.7.1)
|
||||
unicorn (5.2.0)
|
||||
unicorn (5.3.0)
|
||||
kgio (~> 2.6)
|
||||
raindrops (~> 0.7)
|
||||
uniform_notifier (1.10.0)
|
||||
webmock (3.0.1)
|
||||
addressable (>= 2.3.6)
|
||||
crack (>= 0.3.2)
|
||||
hashdiff
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
@ -394,11 +400,12 @@ DEPENDENCIES
|
||||
barber
|
||||
better_errors
|
||||
binding_of_caller
|
||||
bootsnap
|
||||
bullet
|
||||
byebug
|
||||
certified
|
||||
discourse-qunit-rails
|
||||
discourse_fastimage (= 2.0.3)
|
||||
discourse_image_optim
|
||||
email_reply_trimmer (= 0.1.6)
|
||||
ember-handlebars-template (= 0.7.5)
|
||||
ember-rails (= 0.18.5)
|
||||
@ -410,6 +417,7 @@ DEPENDENCIES
|
||||
fast_blank
|
||||
fast_xor
|
||||
fast_xs
|
||||
fastimage (= 2.1.0)
|
||||
flamegraph
|
||||
foreman
|
||||
gc_tracer
|
||||
@ -417,8 +425,7 @@ DEPENDENCIES
|
||||
hiredis
|
||||
htmlentities
|
||||
http_accept_language (~> 2.0.5)
|
||||
image_optim (= 0.20.2)
|
||||
listen (= 0.7.3)
|
||||
listen
|
||||
logster
|
||||
lru_redux
|
||||
mail
|
||||
@ -467,21 +474,21 @@ DEPENDENCIES
|
||||
rtlit
|
||||
ruby-readability
|
||||
sanitize
|
||||
sass
|
||||
sass-rails
|
||||
sassc
|
||||
seed-fu (~> 2.3.5)
|
||||
shoulda
|
||||
sidekiq
|
||||
sidekiq-statistic
|
||||
simple-rss
|
||||
sinatra
|
||||
spork-rails
|
||||
stackprof
|
||||
thin
|
||||
test_after_commit
|
||||
thor
|
||||
timecop
|
||||
uglifier
|
||||
unf
|
||||
unicorn
|
||||
webmock
|
||||
|
||||
BUNDLED WITH
|
||||
1.13.7
|
||||
1.14.6
|
||||
|
||||
32
README.md
32
README.md
@ -1,24 +1,24 @@
|
||||
<a href="http://www.discourse.org/"></a>
|
||||
|
||||
Discourse is the 100% open source discussion platform built for the next decade of the Internet. It works as:
|
||||
Discourse is the 100% open source discussion platform built for the next decade of the Internet. Use it as a:
|
||||
|
||||
- a mailing list
|
||||
- a discussion forum
|
||||
- a long-form chat room
|
||||
- mailing list
|
||||
- discussion forum
|
||||
- long-form chat room
|
||||
|
||||
To learn more about the philosophy and goals of the project, [visit **discourse.org**](http://www.discourse.org).
|
||||
|
||||
## Screenshots
|
||||
|
||||
<a href="https://bbs.boingboing.net"><img src="https://www.discourse.org/faq/14/boing-boing-discourse.png" width="720px"></a>
|
||||
<a href="https://twittercommunity.com/"><img src="https://www.discourse.org/faq/17/twitter-discourse.png" width="720px"></a>
|
||||
<a href="http://discuss.howtogeek.com"><img src="https://www.discourse.org/faq/17/how-to-geek-discourse.png" width="720px"></a>
|
||||
<a href="https://talk.turtlerockstudios.com/"><img src="https://www.discourse.org/faq/17/turtle-rock-discourse.png" width="720px"></a>
|
||||
|
||||
<a href="https://discuss.atom.io"><img src="https://www.discourse.org/faq/17/nexus-7-2013-mobile-discourse.png" alt="Atom" width="430px"></a>
|
||||
<a href="//discourse.soylent.com"><img src="https://www.discourse.org/faq/15/iphone-5s-mobile-discourse.png" alt="Soylent" width="270px"></a>
|
||||
<a href="https://bbs.boingboing.net"><img alt="Boing Boing" src="https://cloud.githubusercontent.com/assets/1385470/25397876/3fe6cdac-29c0-11e7-8a41-9d0c0279f5a3.png" width="720px"></a>
|
||||
<a href="https://twittercommunity.com/"><img src="https://cloud.githubusercontent.com/assets/1385470/25397920/71b24e4c-29c0-11e7-8bcf-7a47b888412e.png" width="720px"></a>
|
||||
<a href="http://discuss.howtogeek.com"><img src="https://cloud.githubusercontent.com/assets/1385470/25398049/f0995962-29c0-11e7-99d7-a3b9c4f0b357.png" width="720px"></a>
|
||||
<a href="https://talk.turtlerockstudios.com/"><img src="https://cloud.githubusercontent.com/assets/1385470/25398115/2d560d96-29c1-11e7-9a96-b0134a4fedff.png" width="720px"></a>
|
||||
|
||||
Browse [lots more notable Discourse instances](http://www.discourse.org/faq/customers/).
|
||||
<img src="https://www.discourse.org/a/img/about/mobile-devices-2x.jpg" alt="Mobile" width="414">
|
||||
|
||||
Browse [lots more notable Discourse instances](https://www.discourse.org/customers).
|
||||
|
||||
## Development
|
||||
|
||||
@ -38,12 +38,12 @@ If you're looking for business class hosting, see [discourse.org/buy](https://ww
|
||||
|
||||
Discourse is built for the *next* 10 years of the Internet, so our requirements are high:
|
||||
|
||||
| Browsers | Tablets | Smartphones |
|
||||
| Browsers | Tablets | Phones |
|
||||
| -------- | ------- | ----------- |
|
||||
| Safari 6.1+| iPad 2+ | iOS 7+ |
|
||||
| Google Chrome 23+ | Android 4.3+ | Android 4.3+ |
|
||||
| Internet Explorer 11+ | Windows 8 | Windows Phone 8 |
|
||||
| Firefox 16+ | |
|
||||
| Safari 6.1+ | iPad 3+ | iOS 8+ |
|
||||
| Google Chrome 32+ | Android 4.3+ | Android 4.3+ |
|
||||
| Internet Explorer 11+ | | |
|
||||
| Firefox 27+ | | |
|
||||
|
||||
## Built With
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
Before Width: | Height: | Size: 357 KiB After Width: | Height: | Size: 434 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
app/assets/images/favicons/pdf_48px.png
Normal file
BIN
app/assets/images/favicons/pdf_48px.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.1 KiB |
BIN
app/assets/images/favicons/pdf_64px.png
Normal file
BIN
app/assets/images/favicons/pdf_64px.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.8 KiB |
20
app/assets/javascripts/admin/adapters/theme.js.es6
Normal file
20
app/assets/javascripts/admin/adapters/theme.js.es6
Normal file
@ -0,0 +1,20 @@
|
||||
import RestAdapter from 'discourse/adapters/rest';
|
||||
|
||||
export default RestAdapter.extend({
|
||||
basePath() {
|
||||
return "/admin/";
|
||||
},
|
||||
|
||||
afterFindAll(results) {
|
||||
let map = {};
|
||||
results.forEach(theme => {map[theme.id] = theme;});
|
||||
results.forEach(theme => {
|
||||
let mapped = theme.get("child_themes") || [];
|
||||
mapped = mapped.map(t => map[t.id]);
|
||||
theme.set("childThemes", mapped);
|
||||
});
|
||||
return results;
|
||||
},
|
||||
|
||||
jsonMode: true
|
||||
});
|
||||
@ -1,12 +1,21 @@
|
||||
import loadScript from 'discourse/lib/load-script';
|
||||
import { observes } from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
const LOAD_ASYNC = !Ember.Test;
|
||||
|
||||
export default Ember.Component.extend({
|
||||
mode: 'css',
|
||||
classNames: ['ace-wrapper'],
|
||||
_editor: null,
|
||||
_skipContentChangeEvent: null,
|
||||
|
||||
@observes('editorId')
|
||||
editorIdChanged() {
|
||||
if (this.get('autofocus')) {
|
||||
this.send('focus');
|
||||
}
|
||||
},
|
||||
|
||||
@observes('content')
|
||||
contentChanged() {
|
||||
if (this._editor && !this._skipContentChangeEvent) {
|
||||
@ -14,6 +23,13 @@ export default Ember.Component.extend({
|
||||
}
|
||||
},
|
||||
|
||||
@observes('mode')
|
||||
modeChanged() {
|
||||
if (LOAD_ASYNC && this._editor && !this._skipContentChangeEvent) {
|
||||
this._editor.getSession().setMode("ace/mode/" + this.get('mode'));
|
||||
}
|
||||
},
|
||||
|
||||
_destroyEditor: function() {
|
||||
if (this._editor) {
|
||||
this._editor.destroy();
|
||||
@ -23,6 +39,9 @@ export default Ember.Component.extend({
|
||||
// xxx: don't run during qunit tests
|
||||
this.appEvents.off('ace:resize', this, this.resize);
|
||||
}
|
||||
|
||||
$(window).off('ace:resize');
|
||||
|
||||
}.on('willDestroyElement'),
|
||||
|
||||
resize() {
|
||||
@ -39,23 +58,47 @@ export default Ember.Component.extend({
|
||||
if (!this.element || this.isDestroying || this.isDestroyed) { return; }
|
||||
const editor = loadedAce.edit(this.$('.ace')[0]);
|
||||
|
||||
editor.setTheme("ace/theme/chrome");
|
||||
if (LOAD_ASYNC) {
|
||||
editor.setTheme("ace/theme/chrome");
|
||||
}
|
||||
editor.setShowPrintMargin(false);
|
||||
editor.getSession().setMode("ace/mode/" + this.get('mode'));
|
||||
editor.setOptions({fontSize: "14px"});
|
||||
if (LOAD_ASYNC) {
|
||||
editor.getSession().setMode("ace/mode/" + this.get('mode'));
|
||||
}
|
||||
editor.on('change', () => {
|
||||
this._skipContentChangeEvent = true;
|
||||
this.set('content', editor.getSession().getValue());
|
||||
this._skipContentChangeEvent = false;
|
||||
});
|
||||
editor.$blockScrolling = Infinity;
|
||||
editor.renderer.setScrollMargin(10,10);
|
||||
|
||||
this.$().data('editor', editor);
|
||||
this._editor = editor;
|
||||
|
||||
$(window).off('ace:resize').on('ace:resize', ()=>{
|
||||
this.appEvents.trigger('ace:resize');
|
||||
});
|
||||
|
||||
if (this.appEvents) {
|
||||
// xxx: don't run during qunit tests
|
||||
this.appEvents.on('ace:resize', self, self.resize);
|
||||
this.appEvents.on('ace:resize', ()=>this.resize());
|
||||
}
|
||||
|
||||
if (this.get("autofocus")) {
|
||||
this.send("focus");
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
actions: {
|
||||
focus() {
|
||||
if (this._editor) {
|
||||
this._editor.focus();
|
||||
this._editor.navigateFileEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -0,0 +1,33 @@
|
||||
import { iconHTML } from 'discourse-common/helpers/fa-icon';
|
||||
import { bufferedRender } from 'discourse-common/lib/buffered-render';
|
||||
|
||||
export default Ember.Component.extend(bufferedRender({
|
||||
tagName: 'th',
|
||||
classNames: ['sortable'],
|
||||
rerenderTriggers: ['order', 'ascending'],
|
||||
|
||||
buildBuffer(buffer) {
|
||||
const icon = this.get('icon');
|
||||
|
||||
if (icon) {
|
||||
buffer.push(iconHTML(icon));
|
||||
}
|
||||
|
||||
buffer.push(I18n.t(this.get('i18nKey')));
|
||||
|
||||
if (this.get('field') === this.get('order')) {
|
||||
buffer.push(iconHTML(this.get('ascending') ? 'chevron-up' : 'chevron-down'));
|
||||
}
|
||||
},
|
||||
|
||||
click() {
|
||||
const currentOrder = this.get('order');
|
||||
const field = this.get('field');
|
||||
|
||||
if (currentOrder === field) {
|
||||
this.set('ascending', this.get('ascending') ? null : true);
|
||||
} else {
|
||||
this.setProperties({ order: field, ascending: null });
|
||||
}
|
||||
}
|
||||
}));
|
||||
11
app/assets/javascripts/admin/components/admin-wrapper.js.es6
Normal file
11
app/assets/javascripts/admin/components/admin-wrapper.js.es6
Normal file
@ -0,0 +1,11 @@
|
||||
export default Ember.Component.extend({
|
||||
didInsertElement() {
|
||||
this._super();
|
||||
$('body').addClass('admin-interface');
|
||||
},
|
||||
|
||||
willDestroyElement() {
|
||||
this._super();
|
||||
$('body').removeClass('admin-interface');
|
||||
}
|
||||
});
|
||||
@ -1,3 +1,5 @@
|
||||
import {default as loadScript, loadCSS } from 'discourse/lib/load-script';
|
||||
|
||||
/**
|
||||
An input field for a color.
|
||||
|
||||
@ -6,19 +8,36 @@
|
||||
@params valid is a boolean indicating if the input field is a valid color.
|
||||
**/
|
||||
export default Ember.Component.extend({
|
||||
classNames: ['color-picker'],
|
||||
hexValueChanged: function() {
|
||||
var hex = this.get('hexValue');
|
||||
let $text = this.$('input.hex-input');
|
||||
|
||||
if (this.get('valid')) {
|
||||
this.$('input').attr('style', 'color: ' + (this.get('brightnessValue') > 125 ? 'black' : 'white') + '; background-color: #' + hex + ';');
|
||||
$text.attr('style', 'color: ' + (this.get('brightnessValue') > 125 ? 'black' : 'white') + '; background-color: #' + hex + ';');
|
||||
|
||||
if (this.get('pickerLoaded')) {
|
||||
this.$('.picker').spectrum({color: "#" + this.get('hexValue')});
|
||||
}
|
||||
} else {
|
||||
this.$('input').attr('style', '');
|
||||
$text.attr('style', '');
|
||||
}
|
||||
}.observes('hexValue', 'brightnessValue', 'valid'),
|
||||
|
||||
_triggerHexChanged: function() {
|
||||
var self = this;
|
||||
Em.run.schedule('afterRender', function() {
|
||||
self.hexValueChanged();
|
||||
didInsertElement() {
|
||||
loadScript('/javascripts/spectrum.js').then(()=>{
|
||||
loadCSS('/javascripts/spectrum.css').then(()=>{
|
||||
Em.run.schedule('afterRender', ()=>{
|
||||
this.$('.picker').spectrum({color: "#" + this.get('hexValue')})
|
||||
.on("change.spectrum", (me, color)=>{
|
||||
this.set('hexValue', color.toHexString().replace("#",""));
|
||||
});
|
||||
this.set('pickerLoaded', true);
|
||||
});
|
||||
});
|
||||
});
|
||||
}.on('didInsertElement')
|
||||
Em.run.schedule('afterRender', ()=>{
|
||||
this.hexValueChanged();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
import { getOwner } from 'discourse-common/lib/get-owner';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
router: function() {
|
||||
return getOwner(this).lookup('router:main');
|
||||
}.property(),
|
||||
|
||||
active: function() {
|
||||
const id = this.get('customization.id');
|
||||
return this.get('router.url').indexOf(`/customize/css_html/${id}/css`) !== -1;
|
||||
}.property('router.url', 'customization.id')
|
||||
});
|
||||
@ -1,14 +0,0 @@
|
||||
export default Ember.Component.extend({
|
||||
willInsertElement() {
|
||||
this._super();
|
||||
if (this.session.get("disableCustomCSS")) {
|
||||
$("link.custom-css").attr("rel", "");
|
||||
this.session.set("disableCustomCSS", false);
|
||||
}
|
||||
},
|
||||
|
||||
willDestroyElement() {
|
||||
this._super();
|
||||
$("link.custom-css").attr("rel", "stylesheet");
|
||||
}
|
||||
});
|
||||
@ -30,7 +30,7 @@ export default Ember.Component.extend(bufferedProperty('host'), {
|
||||
save() {
|
||||
if (this.get('cantSave')) { return; }
|
||||
|
||||
const props = this.get('buffered').getProperties('host', 'path_whitelist');
|
||||
const props = this.get('buffered').getProperties('host', 'path_whitelist', 'class_name');
|
||||
props.category_id = this.get('categoryId');
|
||||
|
||||
const host = this.get('host');
|
||||
|
||||
@ -0,0 +1,36 @@
|
||||
import {default as computed, observes} from "ember-addons/ember-computed-decorators";
|
||||
|
||||
export default Ember.Component.extend({
|
||||
init(){
|
||||
this._super();
|
||||
this.set("checkedInternal", this.get("checked"));
|
||||
},
|
||||
|
||||
classNames: ['inline-edit'],
|
||||
|
||||
@observes("checked")
|
||||
checkedChanged() {
|
||||
this.set("checkedInternal", this.get("checked"));
|
||||
},
|
||||
|
||||
@computed("labelKey")
|
||||
label(key) {
|
||||
return I18n.t(key);
|
||||
},
|
||||
|
||||
@computed("checked", "checkedInternal")
|
||||
changed(checked, checkedInternal) {
|
||||
return (!!checked) !== (!!checkedInternal);
|
||||
},
|
||||
|
||||
actions: {
|
||||
cancelled(){
|
||||
this.set("checkedInternal", this.get("checked"));
|
||||
},
|
||||
|
||||
finished(){
|
||||
this.set("checked", this.get("checkedInternal"));
|
||||
this.sendAction();
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -1,4 +1,3 @@
|
||||
import DiscourseURL from 'discourse/lib/url';
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
@ -39,7 +38,11 @@ export default Ember.Controller.extend({
|
||||
},
|
||||
|
||||
download(backup) {
|
||||
DiscourseURL.redirectTo(backup.get('link'));
|
||||
let link = backup.get('filename');
|
||||
ajax("/admin/backups/" + link, { type: "PUT" })
|
||||
.then(() => {
|
||||
bootbox.alert(I18n.t("admin.backups.operations.download.alert"));
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@ -48,7 +51,7 @@ export default Ember.Controller.extend({
|
||||
ajax("/admin/backups/readonly", {
|
||||
type: "PUT",
|
||||
data: { enable: enable }
|
||||
}).then(function() {
|
||||
}).then(() => {
|
||||
site.set("isReadOnly", enable);
|
||||
});
|
||||
}
|
||||
|
||||
@ -0,0 +1,74 @@
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
@computed("model.colors","onlyOverridden")
|
||||
colors(allColors, onlyOverridden) {
|
||||
if (onlyOverridden) {
|
||||
return allColors.filter(color => color.get("overridden"));
|
||||
} else {
|
||||
return allColors;
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
|
||||
revert: function(color) {
|
||||
color.revert();
|
||||
},
|
||||
|
||||
undo: function(color) {
|
||||
color.undo();
|
||||
},
|
||||
|
||||
copyToClipboard() {
|
||||
$(".table.colors").hide();
|
||||
let area = $("<textarea id='copy-range'></textarea>");
|
||||
$(".table.colors").after(area);
|
||||
area.text(this.get("model").schemeJson());
|
||||
let range = document.createRange();
|
||||
range.selectNode(area[0]);
|
||||
window.getSelection().addRange(range);
|
||||
let successful = document.execCommand('copy');
|
||||
if (successful) {
|
||||
this.set("model.savingStatus", I18n.t("admin.customize.copied_to_clipboard"));
|
||||
} else {
|
||||
this.set("model.savingStatus", I18n.t("admin.customize.copy_to_clipboard_error"));
|
||||
}
|
||||
|
||||
setTimeout(()=>{
|
||||
this.set("model.savingStatus", null);
|
||||
}, 2000);
|
||||
|
||||
window.getSelection().removeAllRanges();
|
||||
|
||||
$(".table.colors").show();
|
||||
$(area).remove();
|
||||
},
|
||||
|
||||
copy() {
|
||||
var newColorScheme = Em.copy(this.get('model'), true);
|
||||
newColorScheme.set('name', I18n.t('admin.customize.colors.copy_name_prefix') + ' ' + this.get('model.name'));
|
||||
newColorScheme.save().then(()=>{
|
||||
this.get('allColors').pushObject(newColorScheme);
|
||||
this.replaceRoute('adminCustomize.colors.show', newColorScheme);
|
||||
});
|
||||
},
|
||||
|
||||
save: function() {
|
||||
this.get('model').save();
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
|
||||
const model = this.get('model');
|
||||
return bootbox.confirm(I18n.t("admin.customize.colors.delete_confirm"), I18n.t("no_value"), I18n.t("yes_value"), result => {
|
||||
if (result) {
|
||||
model.destroy().then(()=>{
|
||||
this.get('allColors').removeObject(model);
|
||||
this.replaceRoute('adminCustomize.colors');
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -1,10 +1,14 @@
|
||||
export default Ember.Controller.extend({
|
||||
onlyOverridden: false,
|
||||
import showModal from 'discourse/lib/show-modal';
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
baseColorScheme: function() {
|
||||
return this.get('model').findBy('is_base', true);
|
||||
}.property('model.@each.id'),
|
||||
|
||||
baseColorSchemes: function() {
|
||||
return this.get('model').filterBy('is_base', true);
|
||||
}.property('model.@each.id'),
|
||||
|
||||
baseColors: function() {
|
||||
var baseColorsHash = Em.Object.create({});
|
||||
_.each(this.get('baseColorScheme.colors'), function(color){
|
||||
@ -13,99 +17,25 @@ export default Ember.Controller.extend({
|
||||
return baseColorsHash;
|
||||
}.property('baseColorScheme'),
|
||||
|
||||
removeSelected() {
|
||||
this.get('model').removeObject(this.get('selectedItem'));
|
||||
this.set('selectedItem', null);
|
||||
},
|
||||
|
||||
filterContent: function() {
|
||||
if (!this.get('selectedItem')) { return; }
|
||||
|
||||
if (!this.get('onlyOverridden')) {
|
||||
this.set('colors', this.get('selectedItem.colors'));
|
||||
return;
|
||||
}
|
||||
|
||||
const matches = [];
|
||||
_.each(this.get('selectedItem.colors'), function(color){
|
||||
if (color.get('overridden')) matches.pushObject(color);
|
||||
});
|
||||
|
||||
this.set('colors', matches);
|
||||
}.observes('onlyOverridden'),
|
||||
|
||||
updateEnabled: function() {
|
||||
var selectedItem = this.get('selectedItem');
|
||||
if (selectedItem.get('enabled')) {
|
||||
this.get('model').forEach(function(c) {
|
||||
if (c !== selectedItem) {
|
||||
c.set('enabled', false);
|
||||
c.startTrackingChanges();
|
||||
c.notifyPropertyChange('description');
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
selectColorScheme: function(colorScheme) {
|
||||
if (this.get('selectedItem')) { this.get('selectedItem').set('selected', false); }
|
||||
this.set('selectedItem', colorScheme);
|
||||
this.set('colors', colorScheme.get('colors'));
|
||||
colorScheme.set('savingStatus', null);
|
||||
colorScheme.set('selected', true);
|
||||
this.filterContent();
|
||||
|
||||
newColorSchemeWithBase(baseKey) {
|
||||
const base = this.get('baseColorSchemes').findBy('base_scheme_id', baseKey);
|
||||
const newColorScheme = Em.copy(base, true);
|
||||
newColorScheme.set('name', I18n.t('admin.customize.colors.new_name'));
|
||||
newColorScheme.set('base_scheme_id', base.get('base_scheme_id'));
|
||||
newColorScheme.save().then(()=>{
|
||||
this.get('model').pushObject(newColorScheme);
|
||||
newColorScheme.set('savingStatus', null);
|
||||
this.replaceRoute('adminCustomize.colors.show', newColorScheme);
|
||||
});
|
||||
},
|
||||
|
||||
newColorScheme() {
|
||||
const newColorScheme = Em.copy(this.get('baseColorScheme'), true);
|
||||
newColorScheme.set('name', I18n.t('admin.customize.colors.new_name'));
|
||||
this.get('model').pushObject(newColorScheme);
|
||||
this.send('selectColorScheme', newColorScheme);
|
||||
this.set('onlyOverridden', false);
|
||||
showModal('admin-color-scheme-select-base', { model: this.get('baseColorSchemes'), admin: true});
|
||||
},
|
||||
|
||||
revert: function(color) {
|
||||
color.revert();
|
||||
},
|
||||
|
||||
undo: function(color) {
|
||||
color.undo();
|
||||
},
|
||||
|
||||
toggleEnabled: function() {
|
||||
var selectedItem = this.get('selectedItem');
|
||||
selectedItem.toggleProperty('enabled');
|
||||
selectedItem.save({enabledOnly: true});
|
||||
this.updateEnabled();
|
||||
},
|
||||
|
||||
save: function() {
|
||||
this.get('selectedItem').save();
|
||||
this.updateEnabled();
|
||||
},
|
||||
|
||||
copy(colorScheme) {
|
||||
var newColorScheme = Em.copy(colorScheme, true);
|
||||
newColorScheme.set('name', I18n.t('admin.customize.colors.copy_name_prefix') + ' ' + colorScheme.get('name'));
|
||||
this.get('model').pushObject(newColorScheme);
|
||||
this.send('selectColorScheme', newColorScheme);
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
var self = this,
|
||||
item = self.get('selectedItem');
|
||||
|
||||
return bootbox.confirm(I18n.t("admin.customize.colors.delete_confirm"), I18n.t("no_value"), I18n.t("yes_value"), function(result) {
|
||||
if (result) {
|
||||
if (item.get('newRecord')) {
|
||||
self.removeSelected();
|
||||
} else {
|
||||
item.destroy().then(function(){ self.removeSelected(); });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@ -1,78 +0,0 @@
|
||||
import { url } from 'discourse/lib/computed';
|
||||
|
||||
const sections = ['css', 'header', 'top', 'footer', 'head-tag', 'body-tag',
|
||||
'mobile-css', 'mobile-header', 'mobile-top', 'mobile-footer',
|
||||
'embedded-css'];
|
||||
|
||||
const activeSections = {};
|
||||
sections.forEach(function(s) {
|
||||
activeSections[Ember.String.camelize(s) + "Active"] = Ember.computed.equal('section', s);
|
||||
});
|
||||
|
||||
|
||||
export default Ember.Controller.extend(activeSections, {
|
||||
maximized: false,
|
||||
section: null,
|
||||
|
||||
previewUrl: url("model.key", "/?preview-style=%@"),
|
||||
downloadUrl: url('model.id', '/admin/site_customizations/%@'),
|
||||
|
||||
mobile: function() {
|
||||
return this.get('section').indexOf('mobile-') === 0;
|
||||
}.property('section'),
|
||||
|
||||
maximizeIcon: function() {
|
||||
return this.get('maximized') ? 'compress' : 'expand';
|
||||
}.property('maximized'),
|
||||
|
||||
saveButtonText: function() {
|
||||
return this.get('model.isSaving') ? I18n.t('saving') : I18n.t('admin.customize.save');
|
||||
}.property('model.isSaving'),
|
||||
|
||||
saveDisabled: function() {
|
||||
return !this.get('model.changed') || this.get('model.isSaving');
|
||||
}.property('model.changed', 'model.isSaving'),
|
||||
|
||||
adminCustomizeCssHtml: Ember.inject.controller(),
|
||||
|
||||
undoPreviewUrl: url('/?preview-style='),
|
||||
defaultStyleUrl: url('/?preview-style=default'),
|
||||
|
||||
actions: {
|
||||
save() {
|
||||
this.get('model').saveChanges();
|
||||
},
|
||||
|
||||
destroy() {
|
||||
return bootbox.confirm(I18n.t("admin.customize.delete_confirm"), I18n.t("no_value"), I18n.t("yes_value"), result => {
|
||||
if (result) {
|
||||
const model = this.get('model');
|
||||
model.destroyRecord().then(() => {
|
||||
this.get('adminCustomizeCssHtml').get('model').removeObject(model);
|
||||
this.transitionToRoute('adminCustomizeCssHtml');
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
toggleMaximize: function() {
|
||||
this.toggleProperty('maximized');
|
||||
},
|
||||
|
||||
toggleMobile: function() {
|
||||
const section = this.get('section');
|
||||
|
||||
// Try to send to the same tab as before
|
||||
let dest;
|
||||
if (this.get('mobile')) {
|
||||
dest = section.replace('mobile-', '');
|
||||
if (sections.indexOf(dest) === -1) { dest = 'css'; }
|
||||
} else {
|
||||
dest = 'mobile-' + section;
|
||||
if (sections.indexOf(dest) === -1) { dest = 'mobile-css'; }
|
||||
}
|
||||
this.replaceRoute('adminCustomizeCssHtml.show', this.get('model.id'), dest);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@ -0,0 +1,159 @@
|
||||
import { url } from 'discourse/lib/computed';
|
||||
import { default as computed, observes } from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
maximized: false,
|
||||
section: null,
|
||||
|
||||
targets: [
|
||||
{id: 0, name: I18n.t('admin.customize.theme.common')},
|
||||
{id: 1, name: I18n.t('admin.customize.theme.desktop')},
|
||||
{id: 2, name: I18n.t('admin.customize.theme.mobile')}
|
||||
],
|
||||
|
||||
@computed('onlyOverridden')
|
||||
showCommon() {
|
||||
return this.shouldShow('common');
|
||||
},
|
||||
|
||||
@computed('onlyOverridden')
|
||||
showDesktop() {
|
||||
return this.shouldShow('desktop');
|
||||
},
|
||||
|
||||
@computed('onlyOverridden')
|
||||
showMobile() {
|
||||
return this.shouldShow('mobile');
|
||||
},
|
||||
|
||||
@observes('onlyOverridden')
|
||||
onlyOverriddenChanged() {
|
||||
if (this.get('onlyOverridden')) {
|
||||
if (!this.get('model').hasEdited(this.get('currentTargetName'), this.get('fieldName'))) {
|
||||
let target = (this.get('showCommon') && 'common') ||
|
||||
(this.get('showDesktop') && 'desktop') ||
|
||||
(this.get('showMobile') && 'mobile');
|
||||
|
||||
let fields = this.get('model.theme_fields');
|
||||
let field = fields && fields.find(f => (f.target === target));
|
||||
this.replaceRoute('adminCustomizeThemes.edit', this.get('model.id'), target, field && field.name);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
shouldShow(target){
|
||||
if(!this.get("onlyOverridden")) {
|
||||
return true;
|
||||
}
|
||||
return this.get("model").hasEdited(target);
|
||||
},
|
||||
|
||||
currentTarget: 0,
|
||||
|
||||
setTargetName: function(name) {
|
||||
let target;
|
||||
switch(name) {
|
||||
case "common": target = 0; break;
|
||||
case "desktop": target = 1; break;
|
||||
case "mobile": target = 2; break;
|
||||
}
|
||||
|
||||
this.set("currentTarget", target);
|
||||
},
|
||||
|
||||
@computed("currentTarget")
|
||||
currentTargetName(target) {
|
||||
switch(parseInt(target)) {
|
||||
case 0: return "common";
|
||||
case 1: return "desktop";
|
||||
case 2: return "mobile";
|
||||
}
|
||||
},
|
||||
|
||||
@computed("fieldName")
|
||||
activeSectionMode(fieldName) {
|
||||
return fieldName && fieldName.indexOf("scss") > -1 ? "scss" : "html";
|
||||
},
|
||||
|
||||
@computed("currentTargetName", "fieldName", "saving")
|
||||
error(target, fieldName) {
|
||||
return this.get('model').getError(target, fieldName);
|
||||
},
|
||||
|
||||
@computed("fieldName", "currentTargetName")
|
||||
editorId(fieldName, currentTarget) {
|
||||
return fieldName + "|" + currentTarget;
|
||||
},
|
||||
|
||||
@computed("fieldName", "currentTargetName", "model")
|
||||
activeSection: {
|
||||
get(fieldName, target, model) {
|
||||
return model.getField(target, fieldName);
|
||||
},
|
||||
set(value, fieldName, target, model) {
|
||||
model.setField(target, fieldName, value);
|
||||
return value;
|
||||
}
|
||||
},
|
||||
|
||||
@computed("currentTarget", "onlyOverridden")
|
||||
fields(target, onlyOverridden) {
|
||||
let fields = [
|
||||
"scss", "head_tag", "header", "after_header", "body_tag", "footer"
|
||||
];
|
||||
|
||||
if (parseInt(target) === 0) {
|
||||
fields.push("embedded_scss");
|
||||
}
|
||||
|
||||
if (onlyOverridden) {
|
||||
const model = this.get("model");
|
||||
const targetName = this.get("currentTargetName");
|
||||
fields = fields.filter(name => model.hasEdited(targetName, name));
|
||||
}
|
||||
|
||||
return fields.map(name=>{
|
||||
let hash = {
|
||||
key: (`admin.customize.theme.${name}.text`),
|
||||
name: name
|
||||
};
|
||||
|
||||
if (name.indexOf("_tag") > 0) {
|
||||
hash.icon = "file-text-o";
|
||||
}
|
||||
|
||||
hash.title = I18n.t(`admin.customize.theme.${name}.title`);
|
||||
|
||||
return hash;
|
||||
});
|
||||
},
|
||||
|
||||
previewUrl: url('model.id', '/admin/themes/%@/preview'),
|
||||
|
||||
maximizeIcon: function() {
|
||||
return this.get('maximized') ? 'compress' : 'expand';
|
||||
}.property('maximized'),
|
||||
|
||||
saveButtonText: function() {
|
||||
return this.get('model.isSaving') ? I18n.t('saving') : I18n.t('admin.customize.save');
|
||||
}.property('model.isSaving'),
|
||||
|
||||
saveDisabled: function() {
|
||||
return !this.get('model.changed') || this.get('model.isSaving');
|
||||
}.property('model.changed', 'model.isSaving'),
|
||||
|
||||
actions: {
|
||||
save() {
|
||||
this.set('saving', true);
|
||||
this.get('model').saveChanges("theme_fields").finally(()=>{this.set('saving', false);});
|
||||
},
|
||||
|
||||
toggleMaximize: function() {
|
||||
this.toggleProperty('maximized');
|
||||
Em.run.next(()=>{
|
||||
this.appEvents.trigger('ace:resize');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@ -0,0 +1,199 @@
|
||||
import { default as computed } from 'ember-addons/ember-computed-decorators';
|
||||
import { url } from 'discourse/lib/computed';
|
||||
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||
import showModal from 'discourse/lib/show-modal';
|
||||
|
||||
const THEME_UPLOAD_VAR = 2;
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
|
||||
@computed("model", "allThemes")
|
||||
parentThemes(model, allThemes) {
|
||||
let parents = allThemes.filter(theme =>
|
||||
_.contains(theme.get("childThemes"), model));
|
||||
return parents.length === 0 ? null : parents;
|
||||
},
|
||||
|
||||
@computed("model.theme_fields.@each")
|
||||
hasEditedFields(fields) {
|
||||
return fields.any(f=>!Em.isBlank(f.value));
|
||||
},
|
||||
|
||||
@computed('model.theme_fields.@each')
|
||||
editedDescriptions(fields) {
|
||||
let descriptions = [];
|
||||
let description = target => {
|
||||
let current = fields.filter(field => field.target === target && !Em.isBlank(field.value));
|
||||
if (current.length > 0) {
|
||||
let text = I18n.t('admin.customize.theme.'+target);
|
||||
let localized = current.map(f=>I18n.t('admin.customize.theme.'+f.name + '.text'));
|
||||
return text + ": " + localized.join(" , ");
|
||||
}
|
||||
};
|
||||
['common','desktop','mobile'].forEach(target=> {
|
||||
descriptions.push(description(target));
|
||||
});
|
||||
return descriptions.reject(d=>Em.isBlank(d));
|
||||
},
|
||||
|
||||
previewUrl: url('model.id', '/admin/themes/%@/preview'),
|
||||
|
||||
@computed("colorSchemeId", "model.color_scheme_id")
|
||||
colorSchemeChanged(colorSchemeId, existingId) {
|
||||
colorSchemeId = colorSchemeId === null ? null : parseInt(colorSchemeId);
|
||||
return colorSchemeId !== existingId;
|
||||
},
|
||||
|
||||
@computed("availableChildThemes", "model.childThemes.@each", "model", "allowChildThemes")
|
||||
selectableChildThemes(available, childThemes, model, allowChildThemes) {
|
||||
if (!allowChildThemes && (!childThemes || childThemes.length === 0)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let themes = [];
|
||||
available.forEach(t=> {
|
||||
if (!childThemes || (childThemes.indexOf(t) === -1)) {
|
||||
themes.push(t);
|
||||
};
|
||||
});
|
||||
return themes.length === 0 ? null : themes;
|
||||
},
|
||||
|
||||
@computed("allThemes", "allThemes.length", "model")
|
||||
availableChildThemes(allThemes, count) {
|
||||
if (count === 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let excludeIds = [this.get("model.id")];
|
||||
|
||||
let themes = [];
|
||||
allThemes.forEach(theme => {
|
||||
if (excludeIds.indexOf(theme.get("id")) === -1) {
|
||||
themes.push(theme);
|
||||
}
|
||||
});
|
||||
|
||||
return themes;
|
||||
},
|
||||
|
||||
downloadUrl: url('model.id', '/admin/themes/%@'),
|
||||
|
||||
actions: {
|
||||
|
||||
updateToLatest() {
|
||||
this.set("updatingRemote", true);
|
||||
this.get("model").updateToLatest()
|
||||
.catch(popupAjaxError)
|
||||
.finally(()=>{
|
||||
this.set("updatingRemote", false);
|
||||
});
|
||||
},
|
||||
|
||||
checkForThemeUpdates() {
|
||||
this.set("updatingRemote", true);
|
||||
this.get("model").checkForUpdates()
|
||||
.catch(popupAjaxError)
|
||||
.finally(()=>{
|
||||
this.set("updatingRemote", false);
|
||||
});
|
||||
},
|
||||
|
||||
addUploadModal() {
|
||||
showModal('admin-add-upload', {admin: true, name: ''});
|
||||
},
|
||||
|
||||
addUpload(info) {
|
||||
let model = this.get("model");
|
||||
model.setField('common', info.name, '', info.upload_id, THEME_UPLOAD_VAR);
|
||||
model.saveChanges('theme_fields').catch(e => popupAjaxError(e));
|
||||
},
|
||||
|
||||
cancelChangeScheme() {
|
||||
this.set("colorSchemeId", this.get("model.color_scheme_id"));
|
||||
},
|
||||
changeScheme(){
|
||||
let schemeId = this.get("colorSchemeId");
|
||||
this.set("model.color_scheme_id", schemeId === null ? null : parseInt(schemeId));
|
||||
this.get("model").saveChanges("color_scheme_id");
|
||||
},
|
||||
startEditingName() {
|
||||
this.set("oldName", this.get("model.name"));
|
||||
this.set("editingName", true);
|
||||
},
|
||||
cancelEditingName() {
|
||||
this.set("model.name", this.get("oldName"));
|
||||
this.set("editingName", false);
|
||||
},
|
||||
finishedEditingName() {
|
||||
this.get("model").saveChanges("name");
|
||||
this.set("editingName", false);
|
||||
},
|
||||
|
||||
editTheme() {
|
||||
let edit = ()=>this.transitionToRoute('adminCustomizeThemes.edit', this.get('model.id'), 'common', 'scss');
|
||||
|
||||
if (this.get("model.remote_theme")) {
|
||||
bootbox.confirm(I18n.t("admin.customize.theme.edit_confirm"), result => {
|
||||
if (result) {
|
||||
edit();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
edit();
|
||||
}
|
||||
},
|
||||
|
||||
applyDefault() {
|
||||
const model = this.get("model");
|
||||
model.saveChanges("default").then(()=>{
|
||||
if (model.get("default")) {
|
||||
this.get("allThemes").forEach(theme=>{
|
||||
if (theme !== model && theme.get('default')) {
|
||||
theme.set("default", false);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
applyUserSelectable() {
|
||||
this.get("model").saveChanges("user_selectable");
|
||||
},
|
||||
|
||||
addChildTheme() {
|
||||
let themeId = parseInt(this.get("selectedChildThemeId"));
|
||||
let theme = this.get("allThemes").findBy("id", themeId);
|
||||
this.get("model").addChildTheme(theme);
|
||||
},
|
||||
|
||||
removeUpload(upload) {
|
||||
return bootbox.confirm(
|
||||
I18n.t("admin.customize.theme.delete_upload_confirm"),
|
||||
I18n.t("no_value"),
|
||||
I18n.t("yes_value"), result => {
|
||||
if (result) {
|
||||
this.get("model").removeField(upload);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
removeChildTheme(theme) {
|
||||
this.get("model").removeChildTheme(theme);
|
||||
},
|
||||
|
||||
destroy() {
|
||||
return bootbox.confirm(I18n.t("admin.customize.delete_confirm"), I18n.t("no_value"), I18n.t("yes_value"), result => {
|
||||
if (result) {
|
||||
const model = this.get('model');
|
||||
model.destroyRecord().then(() => {
|
||||
this.get('allThemes').removeObject(model);
|
||||
this.transitionToRoute('adminCustomizeThemes');
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
@ -0,0 +1,10 @@
|
||||
import { default as computed } from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
@computed('model', 'model.@each')
|
||||
sortedThemes(themes) {
|
||||
return _.sortBy(themes.content, t => {
|
||||
return [!t.get("default"), !t.get("user_selectable"), t.get("name").toLowerCase()];
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -8,10 +8,6 @@ export default Ember.Controller.extend({
|
||||
showSendEmailForm: Em.computed.notEmpty('model.html_content'),
|
||||
htmlEmpty: Em.computed.empty('model.html_content'),
|
||||
|
||||
iframeSrc: function() {
|
||||
return ('data:text/html;charset=utf-8,' + encodeURI(this.get('model.html_content')));
|
||||
}.property('model.html_content'),
|
||||
|
||||
actions: {
|
||||
refresh() {
|
||||
const model = this.get('model');
|
||||
|
||||
@ -22,9 +22,9 @@ export default Ember.Controller.extend({
|
||||
];
|
||||
}.property(),
|
||||
|
||||
@computed('model.visible', 'model.public', 'model.alias_level')
|
||||
@computed('model.visible', 'model.public')
|
||||
disableMembershipRequestSetting(visible, publicGroup) {
|
||||
return !visible || publicGroup || !this.get('model.canEveryoneMention');
|
||||
return !visible || publicGroup;
|
||||
},
|
||||
|
||||
@computed('model.visible', 'model.allow_membership_requests')
|
||||
|
||||
@ -0,0 +1,4 @@
|
||||
export default Ember.Controller.extend({
|
||||
adminGroupsBulk: Ember.inject.controller(),
|
||||
bulkAddResponse: Ember.computed.alias('adminGroupsBulk.bulkAddResponse')
|
||||
});
|
||||
@ -6,6 +6,7 @@ export default Ember.Controller.extend({
|
||||
users: null,
|
||||
groupId: null,
|
||||
saving: false,
|
||||
bulkAddResponse: null,
|
||||
|
||||
@computed('saving', 'users', 'groupId')
|
||||
buttonDisabled(saving, users, groupId) {
|
||||
@ -24,7 +25,8 @@ export default Ember.Controller.extend({
|
||||
ajax('/admin/groups/bulk', {
|
||||
data: { users, group_id: this.get('groupId') },
|
||||
method: 'PUT'
|
||||
}).then(() => {
|
||||
}).then(result => {
|
||||
this.set('bulkAddResponse', result);
|
||||
this.transitionToRoute('adminGroups.bulkComplete');
|
||||
}).catch(popupAjaxError).finally(() => {
|
||||
this.set('saving', false);
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
adminGroupsType: Ember.inject.controller(),
|
||||
sortedGroups: Ember.computed.alias("adminGroupsType.sortedGroups"),
|
||||
|
||||
@computed("sortedGroups")
|
||||
messageKey(sortedGroups) {
|
||||
return `admin.groups.${sortedGroups.length > 0 ? 'none_selected' : 'no_custom_groups'}`;
|
||||
}
|
||||
});
|
||||
@ -5,9 +5,20 @@ import StaffActionLog from 'admin/models/staff-action-log';
|
||||
export default Ember.Controller.extend({
|
||||
loading: false,
|
||||
filters: null,
|
||||
userHistoryActions: [],
|
||||
|
||||
filtersExists: Ember.computed.gt('filterCount', 0),
|
||||
|
||||
filterActionIdChanged: function(){
|
||||
const filterActionId = this.get('filterActionId');
|
||||
if (filterActionId) {
|
||||
this._changeFilters({
|
||||
action_name: this.get('userHistoryActions').findBy("id", parseInt(filterActionId,10)).name_raw,
|
||||
action_id: filterActionId
|
||||
});
|
||||
}
|
||||
}.observes('filterActionId'),
|
||||
|
||||
actionFilter: function() {
|
||||
var name = this.get('filters.action_name');
|
||||
if (name) {
|
||||
@ -20,7 +31,6 @@ export default Ember.Controller.extend({
|
||||
showInstructions: Ember.computed.gt('model.length', 0),
|
||||
|
||||
refresh: function() {
|
||||
var self = this;
|
||||
this.set('loading', true);
|
||||
|
||||
var filters = this.get('filters'),
|
||||
@ -37,10 +47,21 @@ export default Ember.Controller.extend({
|
||||
});
|
||||
this.set('filterCount', count);
|
||||
|
||||
StaffActionLog.findAll(params).then(function(result) {
|
||||
self.set('model', result);
|
||||
}).finally(function() {
|
||||
self.set('loading', false);
|
||||
StaffActionLog.findAll(params).then((result) => {
|
||||
this.set('model', result.staff_action_logs);
|
||||
if (this.get('userHistoryActions').length === 0) {
|
||||
let actionTypes = result.user_history_actions.map(pair => {
|
||||
return {
|
||||
id: pair.id,
|
||||
name: I18n.t("admin.logs.staff_actions.actions." + pair.name),
|
||||
name_raw: pair.name
|
||||
};
|
||||
});
|
||||
actionTypes = _.sortBy(actionTypes, row => row.name);
|
||||
this.set('userHistoryActions', actionTypes);
|
||||
}
|
||||
}).finally(()=>{
|
||||
this.set('loading', false);
|
||||
});
|
||||
},
|
||||
|
||||
@ -63,6 +84,7 @@ export default Ember.Controller.extend({
|
||||
changed.action_name = null;
|
||||
changed.action_id = null;
|
||||
changed.custom_type = null;
|
||||
this.set("filterActionId", null);
|
||||
} else {
|
||||
changed[key] = null;
|
||||
}
|
||||
@ -70,6 +92,7 @@ export default Ember.Controller.extend({
|
||||
},
|
||||
|
||||
clearAllFilters: function() {
|
||||
this.set("filterActionId", null);
|
||||
this.resetFilters();
|
||||
},
|
||||
|
||||
|
||||
@ -1,16 +1,9 @@
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
@computed('model.@each.enabled_setting')
|
||||
adminRoutes() {
|
||||
let routes = [];
|
||||
|
||||
this.get('model').forEach(p => {
|
||||
if (this.siteSettings[p.get('enabled_setting')] && p.get('admin_route')) {
|
||||
routes.push(p.get('admin_route'));
|
||||
adminRoutes: function() {
|
||||
return this.get('model').map(p => {
|
||||
if (p.get('enabled')) {
|
||||
return p.admin_route;
|
||||
}
|
||||
});
|
||||
|
||||
return routes;
|
||||
}
|
||||
}).compact();
|
||||
}.property()
|
||||
});
|
||||
|
||||
@ -4,7 +4,7 @@ import Report from 'admin/models/report';
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
queryParams: ["mode", "start-date", "end-date", "category-id", "group-id"],
|
||||
queryParams: ["mode", "start_date", "end_date", "category_id", "group_id"],
|
||||
viewMode: 'graph',
|
||||
viewingTable: Em.computed.equal('viewMode', 'table'),
|
||||
viewingGraph: Em.computed.equal('viewMode', 'graph'),
|
||||
@ -28,7 +28,15 @@ export default Ember.Controller.extend({
|
||||
|
||||
@computed('model.type')
|
||||
showCategoryOptions(modelType) {
|
||||
return !modelType.match(/_private_messages$/) && !modelType.match(/^page_view_/);
|
||||
return [
|
||||
'topics',
|
||||
'posts',
|
||||
'time_to_first_response_total',
|
||||
'topics_with_no_response',
|
||||
'flags',
|
||||
'likes',
|
||||
'bookmarks'
|
||||
].includes(modelType);
|
||||
},
|
||||
|
||||
@computed('model.type')
|
||||
@ -42,13 +50,13 @@ export default Ember.Controller.extend({
|
||||
this.set("refreshing", true);
|
||||
|
||||
this.setProperties({
|
||||
'start-date': this.get('startDate'),
|
||||
'end-date': this.get('endDate'),
|
||||
'category-id': this.get('categoryId'),
|
||||
'start_date': this.get('startDate'),
|
||||
'end_date': this.get('endDate'),
|
||||
'category_id': this.get('categoryId'),
|
||||
});
|
||||
|
||||
if (this.get('groupId')){
|
||||
this.set('group-id', this.get('groupId'));
|
||||
this.set('group_id', this.get('groupId'));
|
||||
}
|
||||
|
||||
q = Report.find(this.get("model.type"), this.get("startDate"), this.get("endDate"), this.get("categoryId"), this.get("groupId"));
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import debounce from 'discourse/lib/debounce';
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
queryParams: ["filter"],
|
||||
filter: null,
|
||||
onlyOverridden: false,
|
||||
filtered: Ember.computed.notEmpty('filter'),
|
||||
|
||||
@ -1,8 +1,13 @@
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
import CanCheckEmails from 'discourse/mixins/can-check-emails';
|
||||
import { propertyNotEqual, setting } from 'discourse/lib/computed';
|
||||
import { userPath } from 'discourse/lib/url';
|
||||
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
export default Ember.Controller.extend(CanCheckEmails, {
|
||||
editingUsername: false,
|
||||
editingName: false,
|
||||
editingTitle: false,
|
||||
originalPrimaryGroupId: null,
|
||||
availableGroups: null,
|
||||
@ -30,6 +35,11 @@ export default Ember.Controller.extend(CanCheckEmails, {
|
||||
return [];
|
||||
}.property('model.user_fields.[]'),
|
||||
|
||||
@computed('model.username_lower')
|
||||
preferencesPath(username) {
|
||||
return userPath(`${username}/preferences`);
|
||||
},
|
||||
|
||||
actions: {
|
||||
|
||||
impersonate() { return this.get("model").impersonate(); },
|
||||
@ -54,23 +64,58 @@ export default Ember.Controller.extend(CanCheckEmails, {
|
||||
anonymize() { return this.get('model').anonymize(); },
|
||||
destroy() { return this.get('model').destroy(); },
|
||||
|
||||
toggleUsernameEdit() {
|
||||
this.set('userUsernameValue', this.get('model.username'));
|
||||
this.toggleProperty('editingUsername');
|
||||
},
|
||||
|
||||
saveUsername() {
|
||||
const oldUsername = this.get('model.username');
|
||||
this.set('model.username', this.get('userUsernameValue'));
|
||||
|
||||
return ajax(`/users/${oldUsername.toLowerCase()}/preferences/username`, {
|
||||
data: { new_username: this.get('userUsernameValue') },
|
||||
type: 'PUT'
|
||||
}).catch(e => {
|
||||
this.set('model.username', oldUsername);
|
||||
popupAjaxError(e);
|
||||
}).finally(() => this.toggleProperty('editingUsername'));
|
||||
},
|
||||
|
||||
toggleNameEdit() {
|
||||
this.set('userNameValue', this.get('model.name'));
|
||||
this.toggleProperty('editingName');
|
||||
},
|
||||
|
||||
saveName() {
|
||||
const oldName = this.get('model.name');
|
||||
this.set('model.name', this.get('userNameValue'));
|
||||
|
||||
return ajax(userPath(`${this.get('model.username').toLowerCase()}.json`), {
|
||||
data: { name: this.get('userNameValue') },
|
||||
type: 'PUT'
|
||||
}).catch(e => {
|
||||
this.set('model.name', oldName);
|
||||
popupAjaxError(e);
|
||||
}).finally(() => this.toggleProperty('editingName'));
|
||||
},
|
||||
|
||||
toggleTitleEdit() {
|
||||
this.set('userTitleValue', this.get('model.title'));
|
||||
this.toggleProperty('editingTitle');
|
||||
},
|
||||
|
||||
saveTitle() {
|
||||
const self = this;
|
||||
const prevTitle = this.get('userTitleValue');
|
||||
|
||||
return ajax(`/users/${this.get('model.username').toLowerCase()}.json`, {
|
||||
this.set('model.title', this.get('userTitleValue'));
|
||||
return ajax(userPath(`${this.get('model.username').toLowerCase()}.json`), {
|
||||
data: {title: this.get('userTitleValue')},
|
||||
type: 'PUT'
|
||||
}).catch(function(e) {
|
||||
bootbox.alert(I18n.t("generic_error_with_reason", {error: "http: " + e.status + " - " + e.body}));
|
||||
}).finally(function() {
|
||||
self.set('model.title', self.get('userTitleValue'));
|
||||
self.toggleProperty('editingTitle');
|
||||
});
|
||||
}).catch(e => {
|
||||
this.set('model.title', prevTitle);
|
||||
popupAjaxError(e);
|
||||
}).finally(() => this.toggleProperty('editingTitle'));
|
||||
},
|
||||
|
||||
generateApiKey() {
|
||||
|
||||
@ -1,9 +1,14 @@
|
||||
import debounce from 'discourse/lib/debounce';
|
||||
import { i18n } from 'discourse/lib/computed';
|
||||
import AdminUser from 'admin/models/admin-user';
|
||||
import { observes } from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
query: null,
|
||||
queryParams: ['order', 'ascending'],
|
||||
order: null,
|
||||
ascending: null,
|
||||
showEmails: false,
|
||||
refreshing: false,
|
||||
listFilter: null,
|
||||
@ -39,14 +44,15 @@ export default Ember.Controller.extend({
|
||||
this._refreshUsers();
|
||||
}, 250).observes('listFilter'),
|
||||
|
||||
|
||||
@observes('order', 'ascending')
|
||||
_refreshUsers: function() {
|
||||
var self = this;
|
||||
this.set('refreshing', true);
|
||||
|
||||
AdminUser.findAll(this.get('query'), { filter: this.get('listFilter'), show_emails: this.get('showEmails') }).then(function (result) {
|
||||
self.set('model', result);
|
||||
}).finally(function() {
|
||||
self.set('refreshing', false);
|
||||
AdminUser.findAll(this.get('query'), { filter: this.get('listFilter'), show_emails: this.get('showEmails'), order: this.get('order'), ascending: this.get('ascending') }).then( (result) => {
|
||||
this.set('model', result);
|
||||
}).finally( () => {
|
||||
this.set('refreshing', false);
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@ -0,0 +1,63 @@
|
||||
import ModalFunctionality from 'discourse/mixins/modal-functionality';
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
import { default as computed, observes } from 'ember-addons/ember-computed-decorators';
|
||||
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||
|
||||
export default Ember.Controller.extend(ModalFunctionality, {
|
||||
adminCustomizeThemesShow: Ember.inject.controller(),
|
||||
|
||||
onShow() {
|
||||
this.set('name', null);
|
||||
this.set('fileSelected', false);
|
||||
},
|
||||
|
||||
enabled: Em.computed.and('nameValid', 'fileSelected'),
|
||||
disabled: Em.computed.not('enabled'),
|
||||
|
||||
@computed('name')
|
||||
nameValid(name) {
|
||||
return name && name.match(/^[a-zA-Z0-9-_]+$/);
|
||||
},
|
||||
|
||||
@observes('name')
|
||||
uploadChanged(){
|
||||
let file = $('#file-input')[0];
|
||||
this.set('fileSelected', file && file.files[0]);
|
||||
},
|
||||
|
||||
actions: {
|
||||
updateName() {
|
||||
let name = this.get('name');
|
||||
if (Em.isEmpty(name)) {
|
||||
name = $('#file-input')[0].files[0].name;
|
||||
this.set('name', name.split(".")[0]);
|
||||
}
|
||||
this.uploadChanged();
|
||||
},
|
||||
upload() {
|
||||
|
||||
let options = {
|
||||
type: 'POST'
|
||||
};
|
||||
|
||||
options.processData = false;
|
||||
options.contentType = false;
|
||||
options.data = new FormData();
|
||||
let file = $('#file-input')[0].files[0];
|
||||
options.data.append('file', file);
|
||||
|
||||
ajax('/admin/themes/upload_asset', options).then(result=>{
|
||||
let upload = {
|
||||
upload_id: result.upload_id,
|
||||
name: this.get('name'),
|
||||
original_filename: file.name
|
||||
};
|
||||
this.get('adminCustomizeThemesShow').send('addUpload', upload);
|
||||
this.send('closeModal');
|
||||
}).catch(e => {
|
||||
popupAjaxError(e);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,14 @@
|
||||
import ModalFunctionality from 'discourse/mixins/modal-functionality';
|
||||
|
||||
export default Ember.Controller.extend(ModalFunctionality, {
|
||||
|
||||
adminCustomizeColors: Ember.inject.controller(),
|
||||
|
||||
actions: {
|
||||
selectBase() {
|
||||
this.get('adminCustomizeColors')
|
||||
.send('newColorSchemeWithBase', this.get('selectedBaseThemeId'));
|
||||
this.send('closeModal');
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,35 @@
|
||||
import ModalFunctionality from 'discourse/mixins/modal-functionality';
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
// import computed from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
export default Ember.Controller.extend(ModalFunctionality, {
|
||||
local: Ember.computed.equal('selection', 'local'),
|
||||
remote: Ember.computed.equal('selection', 'remote'),
|
||||
selection: 'local',
|
||||
adminCustomizeThemes: Ember.inject.controller(),
|
||||
|
||||
actions: {
|
||||
importTheme() {
|
||||
|
||||
let options = {
|
||||
type: 'POST'
|
||||
};
|
||||
|
||||
if (this.get('local')) {
|
||||
options.processData = false;
|
||||
options.contentType = false;
|
||||
options.data = new FormData();
|
||||
options.data.append('theme', $('#file-input')[0].files[0]);
|
||||
} else {
|
||||
options.data = {remote: this.get('uploadUrl')};
|
||||
}
|
||||
|
||||
ajax('/admin/themes/import', options).then(result=>{
|
||||
const theme = this.store.createRecord('theme',result.theme);
|
||||
this.get('adminCustomizeThemes').send('addTheme', theme);
|
||||
this.send('closeModal');
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -2,6 +2,7 @@ import ModalFunctionality from 'discourse/mixins/modal-functionality';
|
||||
import IncomingEmail from 'admin/models/incoming-email';
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
import { longDate } from 'discourse/lib/formatter';
|
||||
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||
|
||||
export default Ember.Controller.extend(ModalFunctionality, {
|
||||
|
||||
@ -12,6 +13,15 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
||||
|
||||
load(id) {
|
||||
return IncomingEmail.find(id).then(result => this.set("model", result));
|
||||
},
|
||||
|
||||
loadFromBounced(id) {
|
||||
return IncomingEmail.findByBounced(id)
|
||||
.then(result => this.set("model", result))
|
||||
.catch(error => {
|
||||
this.send("closeModal");
|
||||
popupAjaxError(error);
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@ -0,0 +1,13 @@
|
||||
import ModalFunctionality from 'discourse/mixins/modal-functionality';
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
|
||||
export default Ember.Controller.extend(ModalFunctionality, {
|
||||
loadDiff() {
|
||||
this.set('loading', true);
|
||||
ajax('/admin/logs/staff_action_logs/' + this.get('model.id') + '/diff')
|
||||
.then(diff=>{
|
||||
this.set('loading', false);
|
||||
this.set('diff', diff.side_by_side);
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -1,20 +0,0 @@
|
||||
import ModalFunctionality from 'discourse/mixins/modal-functionality';
|
||||
|
||||
export default Ember.Controller.extend(ModalFunctionality, {
|
||||
previousSelected: Ember.computed.equal('selectedTab', 'previous'),
|
||||
newSelected: Ember.computed.equal('selectedTab', 'new'),
|
||||
|
||||
onShow: function() {
|
||||
this.send("selectNew");
|
||||
},
|
||||
|
||||
actions: {
|
||||
selectNew: function() {
|
||||
this.set('selectedTab', 'new');
|
||||
},
|
||||
|
||||
selectPrevious: function() {
|
||||
this.set('selectedTab', 'previous');
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -1,7 +0,0 @@
|
||||
import ChangeSiteCustomizationDetailsController from "admin/controllers/modals/change-site-customization-details";
|
||||
|
||||
export default ChangeSiteCustomizationDetailsController.extend({
|
||||
onShow() {
|
||||
this.send("selectPrevious");
|
||||
}
|
||||
});
|
||||
@ -5,6 +5,7 @@ import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||
import ApiKey from 'admin/models/api-key';
|
||||
import Group from 'discourse/models/group';
|
||||
import TL3Requirements from 'admin/models/tl3-requirements';
|
||||
import { userPath } from 'discourse/lib/url';
|
||||
|
||||
const AdminUser = Discourse.User.extend({
|
||||
|
||||
@ -70,7 +71,12 @@ const AdminUser = Discourse.User.extend({
|
||||
groupRemoved(groupId) {
|
||||
return ajax("/admin/users/" + this.get('id') + "/groups/" + groupId, {
|
||||
type: 'DELETE'
|
||||
}).then(() => this.set('groups.[]', this.get('groups').rejectBy("id", groupId)));
|
||||
}).then(() => {
|
||||
this.set('groups.[]', this.get('groups').rejectBy("id", groupId));
|
||||
if (this.get('primary_group_id') === groupId) {
|
||||
this.set('primary_group_id', null);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
revokeApiKey() {
|
||||
@ -114,11 +120,10 @@ const AdminUser = Discourse.User.extend({
|
||||
},
|
||||
|
||||
revokeAdmin() {
|
||||
const self = this;
|
||||
return ajax("/admin/users/" + this.get('id') + "/revoke_admin", {
|
||||
return ajax(`/admin/users/${this.get('id')}/revoke_admin`, {
|
||||
type: 'PUT'
|
||||
}).then(function() {
|
||||
self.setProperties({
|
||||
}).then(() => {
|
||||
this.setProperties({
|
||||
admin: false,
|
||||
can_grant_admin: true,
|
||||
can_revoke_admin: false
|
||||
@ -127,15 +132,10 @@ const AdminUser = Discourse.User.extend({
|
||||
},
|
||||
|
||||
grantAdmin() {
|
||||
const self = this;
|
||||
return ajax("/admin/users/" + this.get('id') + "/grant_admin", {
|
||||
return ajax(`/admin/users/${this.get('id')}/grant_admin`, {
|
||||
type: 'PUT'
|
||||
}).then(function() {
|
||||
self.setProperties({
|
||||
admin: true,
|
||||
can_grant_admin: false,
|
||||
can_revoke_admin: true
|
||||
});
|
||||
}).then(() => {
|
||||
bootbox.alert(I18n.t("admin.user.grant_admin_confirm"));
|
||||
}).catch(popupAjaxError);
|
||||
},
|
||||
|
||||
@ -346,7 +346,7 @@ const AdminUser = Discourse.User.extend({
|
||||
},
|
||||
|
||||
sendActivationEmail() {
|
||||
return ajax('/users/action/send_activation_email', {
|
||||
return ajax(userPath('action/send_activation_email'), {
|
||||
type: 'POST',
|
||||
data: { username: this.get('username') }
|
||||
}).then(function() {
|
||||
|
||||
@ -9,18 +9,26 @@ const ColorScheme = Discourse.Model.extend(Ember.Copyable, {
|
||||
},
|
||||
|
||||
description: function() {
|
||||
return "" + this.name + (this.enabled ? ' (*)' : '');
|
||||
return "" + this.name;
|
||||
}.property(),
|
||||
|
||||
startTrackingChanges: function() {
|
||||
this.set('originals', {
|
||||
name: this.get('name'),
|
||||
enabled: this.get('enabled')
|
||||
name: this.get('name')
|
||||
});
|
||||
},
|
||||
|
||||
schemeJson(){
|
||||
let buffer = [];
|
||||
_.each(this.get('colors'), (c) => {
|
||||
buffer.push(` "${c.get('name')}": "${c.get('hex')}"`);
|
||||
});
|
||||
|
||||
return [`"${this.get("name")}": {`, buffer.join(",\n"), "}"].join("\n");
|
||||
},
|
||||
|
||||
copy: function() {
|
||||
var newScheme = ColorScheme.create({name: this.get('name'), enabled: false, can_edit: true, colors: Em.A()});
|
||||
var newScheme = ColorScheme.create({name: this.get('name'), can_edit: true, colors: Em.A()});
|
||||
_.each(this.get('colors'), function(c){
|
||||
newScheme.colors.pushObject(ColorSchemeColor.create({name: c.get('name'), hex: c.get('hex'), default_hex: c.get('default_hex')}));
|
||||
});
|
||||
@ -29,19 +37,16 @@ const ColorScheme = Discourse.Model.extend(Ember.Copyable, {
|
||||
|
||||
changed: function() {
|
||||
if (!this.originals) return false;
|
||||
if (this.originals['name'] !== this.get('name') || this.originals['enabled'] !== this.get('enabled')) return true;
|
||||
if (this.originals['name'] !== this.get('name')) return true;
|
||||
if (_.any(this.get('colors'), function(c){ return c.get('changed'); })) return true;
|
||||
return false;
|
||||
}.property('name', 'enabled', 'colors.@each.changed', 'saving'),
|
||||
}.property('name', 'colors.@each.changed', 'saving'),
|
||||
|
||||
disableSave: function() {
|
||||
if (this.get('theme_id')) { return false; }
|
||||
return !this.get('changed') || this.get('saving') || _.any(this.get('colors'), function(c) { return !c.get('valid'); });
|
||||
}.property('changed'),
|
||||
|
||||
disableEnable: function() {
|
||||
return !this.get('id') || this.get('saving');
|
||||
}.property('id', 'saving'),
|
||||
|
||||
newRecord: function() {
|
||||
return (!this.get('id'));
|
||||
}.property('id'),
|
||||
@ -53,11 +58,11 @@ const ColorScheme = Discourse.Model.extend(Ember.Copyable, {
|
||||
this.set('savingStatus', I18n.t('saving'));
|
||||
this.set('saving',true);
|
||||
|
||||
var data = { enabled: this.enabled };
|
||||
var data = {};
|
||||
|
||||
if (!opts || !opts.enabledOnly) {
|
||||
data.name = this.name;
|
||||
|
||||
data.base_scheme_id = this.get('base_scheme_id');
|
||||
data.colors = [];
|
||||
_.each(this.get('colors'), function(c) {
|
||||
if (!self.id || c.get('changed')) {
|
||||
@ -78,8 +83,6 @@ const ColorScheme = Discourse.Model.extend(Ember.Copyable, {
|
||||
_.each(self.get('colors'), function(c) {
|
||||
c.startTrackingChanges();
|
||||
});
|
||||
} else {
|
||||
self.set('originals.enabled', data.enabled);
|
||||
}
|
||||
self.set('savingStatus', I18n.t('saved'));
|
||||
self.set('saving', false);
|
||||
@ -96,30 +99,25 @@ const ColorScheme = Discourse.Model.extend(Ember.Copyable, {
|
||||
});
|
||||
|
||||
var ColorSchemes = Ember.ArrayProxy.extend({
|
||||
selectedItemChanged: function() {
|
||||
var selected = this.get('selectedItem');
|
||||
_.each(this.get('content'),function(i) {
|
||||
return i.set('selected', selected === i);
|
||||
});
|
||||
}.observes('selectedItem')
|
||||
});
|
||||
|
||||
ColorScheme.reopenClass({
|
||||
findAll: function() {
|
||||
var colorSchemes = ColorSchemes.create({ content: [], loading: true });
|
||||
ajax('/admin/color_schemes').then(function(all) {
|
||||
return ajax('/admin/color_schemes').then(function(all) {
|
||||
_.each(all, function(colorScheme){
|
||||
colorSchemes.pushObject(ColorScheme.create({
|
||||
id: colorScheme.id,
|
||||
name: colorScheme.name,
|
||||
enabled: colorScheme.enabled,
|
||||
is_base: colorScheme.is_base,
|
||||
theme_id: colorScheme.theme_id,
|
||||
theme_name: colorScheme.theme_name,
|
||||
base_scheme_id: colorScheme.base_scheme_id,
|
||||
colors: colorScheme.colors.map(function(c) { return ColorSchemeColor.create({name: c.name, hex: c.hex, default_hex: c.default_hex}); })
|
||||
}));
|
||||
});
|
||||
colorSchemes.set('loading', false);
|
||||
return colorSchemes;
|
||||
});
|
||||
return colorSchemes;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -19,6 +19,11 @@ IncomingEmail.reopenClass({
|
||||
return ajax(`/admin/email/incoming/${id}.json`);
|
||||
},
|
||||
|
||||
findByBounced(id) {
|
||||
return ajax(`/admin/email/incoming_from_bounced/${id}.json`);
|
||||
},
|
||||
|
||||
|
||||
findAll(filter, offset) {
|
||||
filter = filter || {};
|
||||
offset = offset || 0;
|
||||
|
||||
@ -1,31 +0,0 @@
|
||||
import RestModel from 'discourse/models/rest';
|
||||
|
||||
const trackedProperties = [
|
||||
'enabled', 'name', 'stylesheet', 'header', 'top', 'footer', 'mobile_stylesheet',
|
||||
'mobile_header', 'mobile_top', 'mobile_footer', 'head_tag', 'body_tag', 'embedded_css'
|
||||
];
|
||||
|
||||
function changed() {
|
||||
const originals = this.get('originals');
|
||||
if (!originals) { return false; }
|
||||
return _.some(trackedProperties, (p) => originals[p] !== this.get(p));
|
||||
}
|
||||
|
||||
const SiteCustomization = RestModel.extend({
|
||||
description: function() {
|
||||
return "" + this.name + (this.enabled ? ' (*)' : '');
|
||||
}.property('selected', 'name', 'enabled'),
|
||||
|
||||
changed: changed.property.apply(changed, trackedProperties.concat('originals')),
|
||||
|
||||
startTrackingChanges: function() {
|
||||
this.set('originals', this.getProperties(trackedProperties));
|
||||
}.on('init'),
|
||||
|
||||
saveChanges() {
|
||||
return this.save(this.getProperties(trackedProperties)).then(() => this.startTrackingChanges());
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
export default SiteCustomization;
|
||||
@ -39,7 +39,7 @@ const StaffActionLog = Discourse.Model.extend({
|
||||
}.property('action_name'),
|
||||
|
||||
useCustomModalForDetails: function() {
|
||||
return _.contains(['change_site_customization', 'delete_site_customization'], this.get('action_name'));
|
||||
return _.contains(['change_theme', 'delete_theme'], this.get('action_name'));
|
||||
}.property('action_name')
|
||||
});
|
||||
|
||||
@ -57,10 +57,13 @@ StaffActionLog.reopenClass({
|
||||
},
|
||||
|
||||
findAll: function(filters) {
|
||||
return ajax("/admin/logs/staff_action_logs.json", { data: filters }).then(function(staff_actions) {
|
||||
return staff_actions.map(function(s) {
|
||||
return StaffActionLog.create(s);
|
||||
});
|
||||
return ajax("/admin/logs/staff_action_logs.json", { data: filters }).then((data) => {
|
||||
return {
|
||||
staff_action_logs: data.staff_action_logs.map(function(s) {
|
||||
return StaffActionLog.create(s);
|
||||
}),
|
||||
user_history_actions: data.user_history_actions
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
156
app/assets/javascripts/admin/models/theme.js.es6
Normal file
156
app/assets/javascripts/admin/models/theme.js.es6
Normal file
@ -0,0 +1,156 @@
|
||||
import RestModel from 'discourse/models/rest';
|
||||
import { default as computed } from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
const THEME_UPLOAD_VAR = 2;
|
||||
|
||||
const Theme = RestModel.extend({
|
||||
|
||||
@computed('theme_fields')
|
||||
themeFields(fields) {
|
||||
|
||||
if (!fields) {
|
||||
this.set('theme_fields', []);
|
||||
return {};
|
||||
}
|
||||
|
||||
let hash = {};
|
||||
if (fields) {
|
||||
fields.forEach(field=>{
|
||||
if (!field.type_id || field.type_id < THEME_UPLOAD_VAR) {
|
||||
hash[this.getKey(field)] = field;
|
||||
}
|
||||
});
|
||||
}
|
||||
return hash;
|
||||
},
|
||||
|
||||
@computed('theme_fields', 'theme_fields.@each')
|
||||
uploads(fields) {
|
||||
if (!fields) {
|
||||
return [];
|
||||
}
|
||||
return fields.filter((f)=> f.target === 'common' && f.type_id === THEME_UPLOAD_VAR);
|
||||
},
|
||||
|
||||
getKey(field){
|
||||
return field.target + " " + field.name;
|
||||
},
|
||||
|
||||
hasEdited(target, name){
|
||||
if (name) {
|
||||
return !Em.isEmpty(this.getField(target, name));
|
||||
} else {
|
||||
let fields = this.get("theme_fields") || [];
|
||||
return fields.any(field => (field.target === target && !Em.isEmpty(field.value)));
|
||||
}
|
||||
},
|
||||
|
||||
getError(target, name) {
|
||||
let themeFields = this.get("themeFields");
|
||||
let key = this.getKey({target,name});
|
||||
let field = themeFields[key];
|
||||
return field ? field.error : "";
|
||||
},
|
||||
|
||||
getField(target, name) {
|
||||
let themeFields = this.get("themeFields");
|
||||
let key = this.getKey({target, name});
|
||||
let field = themeFields[key];
|
||||
return field ? field.value : "";
|
||||
},
|
||||
|
||||
removeField(field) {
|
||||
this.set("changed", true);
|
||||
|
||||
field.upload_id = null;
|
||||
field.value = null;
|
||||
|
||||
return this.saveChanges("theme_fields");
|
||||
},
|
||||
|
||||
setField(target, name, value, upload_id, type_id) {
|
||||
this.set("changed", true);
|
||||
let themeFields = this.get("themeFields");
|
||||
let field = {name, target, value, upload_id, type_id};
|
||||
|
||||
// slow path for uploads and so on
|
||||
if (type_id && type_id > 1) {
|
||||
let fields = this.get("theme_fields");
|
||||
let existing = fields.find((f) =>
|
||||
f.target === target &&
|
||||
f.name === name &&
|
||||
f.type_id === type_id);
|
||||
if (existing) {
|
||||
existing.value = value;
|
||||
existing.upload_id = upload_id;
|
||||
} else {
|
||||
fields.push(field);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// fast path
|
||||
let key = this.getKey({target,name});
|
||||
let existingField = themeFields[key];
|
||||
if (!existingField) {
|
||||
this.theme_fields.push(field);
|
||||
themeFields[key] = field;
|
||||
} else {
|
||||
existingField.value = value;
|
||||
}
|
||||
},
|
||||
|
||||
@computed("childThemes.@each")
|
||||
child_theme_ids(childThemes) {
|
||||
if (childThemes) {
|
||||
return childThemes.map(theme => Ember.get(theme, "id"));
|
||||
}
|
||||
},
|
||||
|
||||
removeChildTheme(theme) {
|
||||
const childThemes = this.get("childThemes");
|
||||
childThemes.removeObject(theme);
|
||||
return this.saveChanges("child_theme_ids");
|
||||
},
|
||||
|
||||
addChildTheme(theme){
|
||||
let childThemes = this.get("childThemes");
|
||||
if (!childThemes) {
|
||||
childThemes = [];
|
||||
this.set('childThemes', childThemes);
|
||||
}
|
||||
childThemes.removeObject(theme);
|
||||
childThemes.pushObject(theme);
|
||||
return this.saveChanges("child_theme_ids");
|
||||
},
|
||||
|
||||
@computed('name', 'default')
|
||||
description: function(name, isDefault) {
|
||||
if (isDefault) {
|
||||
return I18n.t('admin.customize.theme.default_name', {name: name});
|
||||
} else {
|
||||
return name;
|
||||
}
|
||||
},
|
||||
|
||||
checkForUpdates() {
|
||||
return this.save({remote_check: true})
|
||||
.then(() => this.set("changed", false));
|
||||
},
|
||||
|
||||
updateToLatest() {
|
||||
return this.save({remote_update: true})
|
||||
.then(() => this.set("changed", false));
|
||||
},
|
||||
|
||||
changed: false,
|
||||
|
||||
saveChanges() {
|
||||
const hash = this.getProperties.apply(this, arguments);
|
||||
return this.save(hash)
|
||||
.then(() => this.set("changed", false));
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
export default Theme;
|
||||
@ -85,7 +85,7 @@ export default Discourse.Route.extend({
|
||||
if (confirmed) {
|
||||
Discourse.User.currentProp("hideReadOnlyAlert", true);
|
||||
backup.restore().then(function() {
|
||||
self.controllerFor("adminBackupsLogs").clear();
|
||||
self.controllerFor("adminBackupsLogs").get("logs").clear();
|
||||
self.controllerFor("adminBackups").set("model.isOperationRunning", true);
|
||||
self.transitionTo("admin.backups.logs");
|
||||
});
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
export default Ember.Route.extend({
|
||||
|
||||
model(params) {
|
||||
const all = this.modelFor('adminCustomize.colors');
|
||||
const model = all.findBy('id', parseInt(params.scheme_id));
|
||||
return model ? model : this.replaceWith('adminCustomize.colors.index');
|
||||
},
|
||||
|
||||
serialize(model) {
|
||||
return {scheme_id: model.get('id')};
|
||||
},
|
||||
|
||||
setupController(controller, model) {
|
||||
controller.set('model', model);
|
||||
controller.set('allColors', this.modelFor('adminCustomize.colors'));
|
||||
}
|
||||
});
|
||||
|
||||
@ -6,9 +6,7 @@ export default Ember.Route.extend({
|
||||
return ColorScheme.findAll();
|
||||
},
|
||||
|
||||
deactivate() {
|
||||
this._super();
|
||||
this.controllerFor('adminCustomizeColors').set('selectedItem', null);
|
||||
},
|
||||
|
||||
setupController(controller, model) {
|
||||
controller.set("model", model);
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,11 +0,0 @@
|
||||
export default Ember.Route.extend({
|
||||
model(params) {
|
||||
const all = this.modelFor('adminCustomizeCssHtml');
|
||||
const model = all.findBy('id', parseInt(params.site_customization_id));
|
||||
return model ? { model, section: params.section } : this.replaceWith('adminCustomizeCssHtml.index');
|
||||
},
|
||||
|
||||
setupController(controller, hash) {
|
||||
controller.setProperties(hash);
|
||||
}
|
||||
});
|
||||
@ -1,26 +0,0 @@
|
||||
import showModal from 'discourse/lib/show-modal';
|
||||
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||
|
||||
export default Ember.Route.extend({
|
||||
model() {
|
||||
return this.store.findAll('site-customization');
|
||||
},
|
||||
|
||||
actions: {
|
||||
importModal() {
|
||||
showModal('upload-customization');
|
||||
},
|
||||
|
||||
newCustomization(obj) {
|
||||
obj = obj || {name: I18n.t("admin.customize.new_style")};
|
||||
const item = this.store.createRecord('site-customization');
|
||||
|
||||
const all = this.modelFor('adminCustomizeCssHtml');
|
||||
const self = this;
|
||||
item.save(obj).then(function() {
|
||||
all.pushObject(item);
|
||||
self.transitionTo('adminCustomizeCssHtml.show', item.get('id'), 'css');
|
||||
}).catch(popupAjaxError);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -1,5 +1,5 @@
|
||||
export default Ember.Route.extend({
|
||||
beforeModel() {
|
||||
this.transitionTo('adminCustomize.colors');
|
||||
this.transitionTo('adminCustomizeThemes');
|
||||
}
|
||||
});
|
||||
|
||||
@ -0,0 +1,27 @@
|
||||
export default Ember.Route.extend({
|
||||
model(params) {
|
||||
const all = this.modelFor('adminCustomizeThemes');
|
||||
const model = all.findBy('id', parseInt(params.theme_id));
|
||||
return model ? { model,
|
||||
target: params.target,
|
||||
field_name: params.field_name
|
||||
} : this.replaceWith('adminCustomizeThemes.index');
|
||||
},
|
||||
|
||||
serialize(wrapper) {
|
||||
return {
|
||||
model: wrapper.model,
|
||||
target: wrapper.target || "common",
|
||||
field_name: wrapper.field_name || "scss",
|
||||
theme_id: wrapper.model.get("id")
|
||||
};
|
||||
},
|
||||
|
||||
setupController(controller, wrapper) {
|
||||
controller.set("model", wrapper.model);
|
||||
controller.setTargetName(wrapper.target || "common");
|
||||
controller.set("fieldName", wrapper.field_name || "scss");
|
||||
this.controllerFor("adminCustomizeThemes").set("editingTheme", true);
|
||||
},
|
||||
|
||||
});
|
||||
@ -0,0 +1,5 @@
|
||||
export default Ember.Route.extend({
|
||||
setupController() {
|
||||
this.controllerFor("adminCustomizeThemes").set("editingTheme", false);
|
||||
},
|
||||
});
|
||||
@ -0,0 +1,21 @@
|
||||
export default Ember.Route.extend({
|
||||
|
||||
serialize(model) {
|
||||
return {theme_id: model.get('id')};
|
||||
},
|
||||
|
||||
model(params) {
|
||||
const all = this.modelFor('adminCustomizeThemes');
|
||||
const model = all.findBy('id', parseInt(params.theme_id));
|
||||
return model ? model : this.replaceWith('adminCustomizeTheme.index');
|
||||
},
|
||||
|
||||
setupController(controller, model) {
|
||||
controller.set("model", model);
|
||||
const parentController = this.controllerFor("adminCustomizeThemes");
|
||||
parentController.set("editingTheme", false);
|
||||
controller.set("allThemes", parentController.get("model"));
|
||||
controller.set("colorSchemes", parentController.get("model.extras.color_schemes"));
|
||||
controller.set("colorSchemeId", model.get("color_scheme_id"));
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,35 @@
|
||||
import showModal from 'discourse/lib/show-modal';
|
||||
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||
|
||||
export default Ember.Route.extend({
|
||||
model() {
|
||||
return this.store.findAll('theme');
|
||||
},
|
||||
|
||||
setupController(controller, model) {
|
||||
this._super(controller, model);
|
||||
controller.set("editingTheme", false);
|
||||
},
|
||||
|
||||
actions: {
|
||||
importModal() {
|
||||
showModal('admin-import-theme', {admin: true});
|
||||
},
|
||||
|
||||
addTheme(theme) {
|
||||
const all = this.modelFor('adminCustomizeThemes');
|
||||
all.pushObject(theme);
|
||||
this.transitionTo('adminCustomizeThemes.show', theme.get('id'));
|
||||
},
|
||||
|
||||
|
||||
newTheme(obj) {
|
||||
obj = obj || {name: I18n.t("admin.customize.new_style")};
|
||||
const item = this.store.createRecord('theme');
|
||||
|
||||
item.save(obj).then(() => {
|
||||
this.send('addTheme', item);
|
||||
}).catch(popupAjaxError);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -1,2 +1,14 @@
|
||||
import showModal from 'discourse/lib/show-modal';
|
||||
import AdminEmailLogs from 'admin/routes/admin-email-logs';
|
||||
export default AdminEmailLogs.extend({ status: "bounced" });
|
||||
|
||||
export default AdminEmailLogs.extend({
|
||||
status: "bounced",
|
||||
|
||||
actions: {
|
||||
showIncomingEmail(id) {
|
||||
showModal('admin-incoming-email', { admin: true });
|
||||
this.controllerFor("modals/admin-incoming-email").loadFromBounced(id);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
export default Discourse.Route.extend({
|
||||
redirect: function() {
|
||||
redirect() {
|
||||
this.replaceWith('adminFlags.list', 'active');
|
||||
}
|
||||
});
|
||||
|
||||
@ -6,11 +6,6 @@ export default Discourse.Route.extend({
|
||||
this.render('admin/templates/logs/staff-action-logs', {into: 'adminLogs'});
|
||||
},
|
||||
|
||||
setupController: function(controller) {
|
||||
controller.resetFilters();
|
||||
controller.refresh();
|
||||
},
|
||||
|
||||
actions: {
|
||||
showDetailsModal(model) {
|
||||
showModal('admin-staff-action-log-details', { model, admin: true });
|
||||
@ -18,14 +13,9 @@ export default Discourse.Route.extend({
|
||||
},
|
||||
|
||||
showCustomDetailsModal(model) {
|
||||
const modalName = (model.action_name + '_details').replace(/\_/g, "-");
|
||||
|
||||
showModal(modalName, {
|
||||
model,
|
||||
admin: true,
|
||||
templateName: 'site-customization-change'
|
||||
});
|
||||
this.controllerFor('modal').set('modalClass', 'tabbed-modal log-details-modal');
|
||||
let modal = showModal('admin-theme-change', { model, admin: true});
|
||||
this.controllerFor('modal').set('modalClass', 'history-modal');
|
||||
modal.loadDiff();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -7,11 +7,11 @@
|
||||
@module Discourse
|
||||
**/
|
||||
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) {
|
||||
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']);
|
||||
},
|
||||
|
||||
setupController: function(controller, model) {
|
||||
|
||||
@ -15,10 +15,14 @@ export default function() {
|
||||
});
|
||||
|
||||
this.route('adminCustomize', { path: '/customize', resetNamespace: true } ,function() {
|
||||
this.route('colors');
|
||||
|
||||
this.route('adminCustomizeCssHtml', { path: 'css_html', resetNamespace: true }, function() {
|
||||
this.route('show', {path: '/:site_customization_id/:section'});
|
||||
this.route('colors', function() {
|
||||
this.route('show', {path: '/:scheme_id'});
|
||||
});
|
||||
|
||||
this.route('adminCustomizeThemes', { path: 'themes', resetNamespace: true }, function() {
|
||||
this.route('show', {path: '/:theme_id'});
|
||||
this.route('edit', {path: '/:theme_id/:target/:field_name/edit'});
|
||||
});
|
||||
|
||||
this.route('adminSiteText', { path: '/site_texts', resetNamespace: true }, function() {
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
import SiteSetting from 'admin/models/site-setting';
|
||||
|
||||
export default Discourse.Route.extend({
|
||||
queryParams: {
|
||||
filter: { replace: true }
|
||||
},
|
||||
|
||||
model() {
|
||||
return SiteSetting.findAll();
|
||||
},
|
||||
|
||||
@ -28,6 +28,14 @@ export default Discourse.Route.extend({
|
||||
showSuspendModal(model) {
|
||||
showModal('admin-suspend-user', { model, admin: true });
|
||||
this.controllerFor('modal').set('modalClass', 'suspend-user-modal');
|
||||
},
|
||||
|
||||
viewActionLogs(username) {
|
||||
const controller = this.controllerFor('adminLogs.staffActionLogs');
|
||||
this.transitionTo('adminLogs.staffActionLogs').then(() => {
|
||||
controller.set('filters', Ember.Object.create());
|
||||
controller._changeFilters({ target_user: username });
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
{{#disable-custom-stylesheets class="container"}}
|
||||
{{#admin-wrapper class="container"}}
|
||||
<div class="row">
|
||||
<div class="full-width">
|
||||
|
||||
@ -34,4 +34,4 @@
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{{/disable-custom-stylesheets}}
|
||||
{{/admin-wrapper}}
|
||||
|
||||
@ -11,11 +11,11 @@
|
||||
{{number report.yesterdayCount}} {{fa-icon "caret-up" class="up"}} {{fa-icon "caret-down" class="down"}}
|
||||
</td>
|
||||
|
||||
<td class="value {{report.sevenDayTrend}}" title={{number report.sevenDayCountTitle}}>
|
||||
<td class="value {{report.sevenDayTrend}}" title={{report.sevenDayCountTitle}}>
|
||||
{{number report.lastSevenDaysCount}} {{fa-icon "caret-up" class="up"}} {{fa-icon "caret-down" class="down"}}
|
||||
</td>
|
||||
|
||||
<td class="value {{report.thirtyDayTrend}}" title={{number report.thirtyDayCountTitle}}>
|
||||
<td class="value {{report.thirtyDayTrend}}" title={{report.thirtyDayCountTitle}}>
|
||||
{{number report.lastThirtyDaysCount}} {{fa-icon "caret-up" class="up"}} {{fa-icon "caret-down" class="down"}}
|
||||
</td>
|
||||
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
<li>
|
||||
<a href="/admin/customize/css_html/{{customization.id}}/css" class="{{if active 'active'}}">
|
||||
{{customization.description}}
|
||||
</a>
|
||||
</li>
|
||||
@ -2,6 +2,9 @@
|
||||
<td>
|
||||
{{input value=buffered.host placeholder="example.com" enter="save" class="host-name"}}
|
||||
</td>
|
||||
<td>
|
||||
{{input value=buffered.class_name placeholder="class" enter="save" class="class-name"}}
|
||||
</td>
|
||||
<td>
|
||||
{{input value=buffered.path_whitelist placeholder="/blog/.*" enter="save" class="path-whitelist"}}
|
||||
</td>
|
||||
@ -14,6 +17,7 @@
|
||||
</td>
|
||||
{{else}}
|
||||
<td>{{host.host}}</td>
|
||||
<td>{{host.class_name}}</td>
|
||||
<td>{{host.path_whitelist}}</td>
|
||||
<td>{{category-badge host.category}}</td>
|
||||
<td>
|
||||
|
||||
@ -0,0 +1,8 @@
|
||||
<label class='checkbox-label'>
|
||||
{{input type="checkbox" disabled=disabled checked=checkedInternal}}
|
||||
{{label}}
|
||||
</label>
|
||||
{{#if changed}}
|
||||
{{d-button action="finished" class="btn-primary btn-small submit-edit" icon="check"}}
|
||||
{{d-button action="cancelled" class="btn-small cancel-edit" icon="times"}}
|
||||
{{/if}}
|
||||
@ -0,0 +1 @@
|
||||
<p class="about">{{i18n 'admin.customize.colors.about'}}</p>
|
||||
@ -0,0 +1,62 @@
|
||||
<div class="color-scheme show-current-style">
|
||||
<div class="admin-container">
|
||||
<h1>{{#if model.theme_id}}{{model.name}}{{else}}{{text-field class="style-name" value=model.name}}{{/if}}</h1>
|
||||
<div class="controls">
|
||||
{{#unless model.theme_id}}
|
||||
<button {{action "save"}} disabled={{model.disableSave}} class='btn'>{{i18n 'admin.customize.save'}}</button>
|
||||
{{/unless}}
|
||||
<button {{action "copy" model}} class='btn'><i class="fa fa-copy"></i> {{i18n 'admin.customize.copy'}}</button>
|
||||
<button {{action "copyToClipboard" model}} class='btn'><i class="fa fa-clipboard"></i> {{i18n 'admin.customize.copy_to_clipboard'}}</button>
|
||||
{{#if model.theme_id}}
|
||||
{{i18n "admin.customize.theme_owner"}}
|
||||
{{#link-to "adminCustomizeThemes.show" model.theme_id}}{{model.theme_name}}{{/link-to}}
|
||||
{{else}}
|
||||
<button {{action "destroy"}} class='btn btn-danger'><i class="fa fa-trash-o"></i> {{i18n 'admin.customize.delete'}}</button>
|
||||
{{/if}}
|
||||
<span class="saving {{unless model.savingStatus 'hidden'}}">{{model.savingStatus}}</span>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
<div class='admin-controls'>
|
||||
<div class='search controls'>
|
||||
<label>
|
||||
{{input type="checkbox" checked=onlyOverridden}}
|
||||
{{i18n 'admin.site_settings.show_overriden'}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if colors.length}}
|
||||
<table class="table colors">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th class="hex">{{i18n 'admin.customize.color'}}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each colors as |c|}}
|
||||
<tr class="{{if c.changed 'changed'}} {{if c.valid 'valid' 'invalid'}}">
|
||||
<td class="name" title={{c.name}}>
|
||||
<b>{{c.translatedName}}</b>
|
||||
<br/>
|
||||
<span class="description">{{c.description}}</span>
|
||||
</td>
|
||||
<td class="hex">{{color-input hexValue=c.hex brightnessValue=c.brightness valid=c.valid}}</td>
|
||||
<td class="actions">
|
||||
{{#unless model.theme_id}}
|
||||
<button class="btn revert {{unless c.savedIsOverriden 'invisible'}}" {{action "revert" c}} title="{{i18n 'admin.customize.colors.revert_title'}}">{{i18n 'revert'}}</button>
|
||||
<button class="btn undo {{unless c.changed 'invisible'}}" {{action "undo" c}} title="{{i18n 'admin.customize.colors.undo_title'}}">{{i18n 'undo'}}</button>
|
||||
{{/unless}}
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{else}}
|
||||
<p>{{i18n 'search.no_results'}}</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
@ -1,78 +1,17 @@
|
||||
<div class='content-list span6'>
|
||||
<div class='content-list span6 color-schemes'>
|
||||
<h3>{{i18n 'admin.customize.colors.long_title'}}</h3>
|
||||
<ul>
|
||||
{{#each model as |scheme|}}
|
||||
{{#unless scheme.is_base}}
|
||||
<li><a {{action "selectColorScheme" scheme}} class="{{if scheme.selected 'active'}}">{{scheme.description}}</a></li>
|
||||
<li>
|
||||
{{#link-to 'adminCustomize.colors.show' scheme replace=true}}{{fa-icon 'paint-brush'}}{{scheme.description}}{{/link-to}}
|
||||
</li>
|
||||
{{/unless}}
|
||||
{{/each}}
|
||||
</ul>
|
||||
<button {{action "newColorScheme"}} class='btn'><i class="fa fa-plus"></i>{{i18n 'admin.customize.new'}}</button>
|
||||
<button {{action "newColorScheme"}} class='btn'>{{fa-icon 'plus'}}{{i18n 'admin.customize.new'}}</button>
|
||||
</div>
|
||||
|
||||
{{#if selectedItem}}
|
||||
<div class="current-style color-scheme">
|
||||
<div class="admin-container">
|
||||
<h1>{{text-field class="style-name" value=selectedItem.name}}</h1>
|
||||
|
||||
<div class="controls">
|
||||
<button {{action "save"}} disabled={{selectedItem.disableSave}} class='btn'>{{i18n 'admin.customize.save'}}</button>
|
||||
<button {{action "toggleEnabled"}} disabled={{selectedItem.disableEnable}} class="btn">
|
||||
{{#if selectedItem.enabled}}
|
||||
{{i18n 'disable'}}
|
||||
{{else}}
|
||||
{{i18n 'enable'}}
|
||||
{{/if}}
|
||||
</button>
|
||||
<button {{action "copy" selectedItem}} class='btn'><i class="fa fa-copy"></i> {{i18n 'admin.customize.copy'}}</button>
|
||||
<button {{action "destroy"}} class='btn btn-danger'><i class="fa fa-trash-o"></i> {{i18n 'admin.customize.delete'}}</button>
|
||||
<span class="saving {{unless selectedItem.savingStatus 'hidden'}}">{{selectedItem.savingStatus}}</span>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
<div class='admin-controls'>
|
||||
<div class='search controls'>
|
||||
<label>
|
||||
{{input type="checkbox" checked=onlyOverridden}}
|
||||
{{i18n 'admin.site_settings.show_overriden'}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if colors.length}}
|
||||
<table class="table colors">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th class="hex">{{i18n 'admin.customize.color'}}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each colors as |c|}}
|
||||
<tr class="{{if c.changed 'changed'}} {{if c.valid 'valid' 'invalid'}}">
|
||||
<td class="name" title={{c.name}}>
|
||||
<b>{{c.translatedName}}</b>
|
||||
<br/>
|
||||
<span class="description">{{c.description}}</span>
|
||||
</td>
|
||||
<td class="hex">{{color-input hexValue=c.hex brightnessValue=c.brightness valid=c.valid}}</td>
|
||||
<td class="actions">
|
||||
<button class="btn revert {{unless c.savedIsOverriden 'invisible'}}" {{action "revert" c}} title="{{i18n 'admin.customize.colors.revert_title'}}">{{i18n 'revert'}}</button>
|
||||
<button class="btn undo {{unless c.changed 'invisible'}}" {{action "undo" c}} title="{{i18n 'admin.customize.colors.undo_title'}}">{{i18n 'undo'}}</button>
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{else}}
|
||||
<p>{{i18n 'search.no_results'}}</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<p class="about">{{i18n 'admin.customize.colors.about'}}</p>
|
||||
{{/if}}
|
||||
{{outlet}}
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
@ -1,75 +0,0 @@
|
||||
<div class="current-style {{if maximized 'maximized'}}">
|
||||
<div class='wrapper'>
|
||||
{{text-field class="style-name" value=model.name}}
|
||||
<a class="btn export" download target="_blank" href={{downloadUrl}}>{{fa-icon "download"}} {{i18n 'admin.export_json.button_text'}}</a>
|
||||
|
||||
<div class='admin-controls'>
|
||||
<ul class="nav nav-pills">
|
||||
{{#if mobile}}
|
||||
<li>{{#link-to 'adminCustomizeCssHtml.show' model.id 'mobile-css' replace=true}}{{i18n "admin.customize.css"}}{{/link-to}}</li>
|
||||
<li>{{#link-to 'adminCustomizeCssHtml.show' model.id 'mobile-header' replace=true}}{{i18n "admin.customize.header"}}{{/link-to}}</li>
|
||||
<li>{{#link-to 'adminCustomizeCssHtml.show' model.id 'mobile-top' replace=true}}{{i18n "admin.customize.top"}}{{/link-to}}</li>
|
||||
<li>{{#link-to 'adminCustomizeCssHtml.show' model.id 'mobile-footer' replace=true}}{{i18n "admin.customize.footer"}}{{/link-to}}</li>
|
||||
{{else}}
|
||||
<li>{{#link-to 'adminCustomizeCssHtml.show' model.id 'css' replace=true}}{{i18n "admin.customize.css"}}{{/link-to}}</li>
|
||||
<li>{{#link-to 'adminCustomizeCssHtml.show' model.id 'header' replace=true}}{{i18n "admin.customize.header"}}{{/link-to}}</li>
|
||||
<li>{{#link-to 'adminCustomizeCssHtml.show' model.id 'top' replace=true}}{{i18n "admin.customize.top"}}{{/link-to}}</li>
|
||||
<li>{{#link-to 'adminCustomizeCssHtml.show' model.id 'footer' replace=true}}{{i18n "admin.customize.footer"}}{{/link-to}}</li>
|
||||
<li>
|
||||
{{#link-to 'adminCustomizeCssHtml.show' model.id 'head-tag'}}
|
||||
{{fa-icon "file-text-o"}} {{i18n 'admin.customize.head_tag.text'}}
|
||||
{{/link-to}}
|
||||
</li>
|
||||
<li>
|
||||
{{#link-to 'adminCustomizeCssHtml.show' model.id 'body-tag'}}
|
||||
{{fa-icon "file-text-o"}} {{i18n 'admin.customize.body_tag.text'}}
|
||||
{{/link-to}}
|
||||
</li>
|
||||
<li>{{#link-to 'adminCustomizeCssHtml.show' model.id 'embedded-css' replace=true}}{{i18n "admin.customize.embedded_css"}}{{/link-to}}</li>
|
||||
{{/if}}
|
||||
<li class='toggle-mobile'>
|
||||
<a class="{{if mobile 'active'}}" {{action "toggleMobile"}}>{{fa-icon "mobile"}}</a>
|
||||
</li>
|
||||
<li class='toggle-maximize'>
|
||||
<a {{action "toggleMaximize"}}>
|
||||
<i class="fa fa-{{maximizeIcon}}"></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="admin-container">
|
||||
{{#if cssActive}}{{ace-editor content=model.stylesheet mode="scss"}}{{/if}}
|
||||
{{#if headerActive}}{{ace-editor content=model.header mode="html"}}{{/if}}
|
||||
{{#if topActive}}{{ace-editor content=model.top mode="html"}}{{/if}}
|
||||
{{#if footerActive}}{{ace-editor content=model.footer mode="html"}}{{/if}}
|
||||
{{#if headTagActive}}{{ace-editor content=model.head_tag mode="html"}}{{/if}}
|
||||
{{#if bodyTagActive}}{{ace-editor content=model.body_tag mode="html"}}{{/if}}
|
||||
{{#if embeddedCssActive}}{{ace-editor content=model.embedded_css mode="css"}}{{/if}}
|
||||
{{#if mobileCssActive}}{{ace-editor content=model.mobile_stylesheet mode="scss"}}{{/if}}
|
||||
{{#if mobileHeaderActive}}{{ace-editor content=model.mobile_header mode="html"}}{{/if}}
|
||||
{{#if mobileTopActive}}{{ace-editor content=model.mobile_top mode="html"}}{{/if}}
|
||||
{{#if mobileFooterActive}}{{ace-editor content=model.mobile_footer mode="html"}}{{/if}}
|
||||
</div>
|
||||
|
||||
<div class='admin-footer'>
|
||||
<div class='status-actions'>
|
||||
<span>{{i18n 'admin.customize.enabled'}} {{input type="checkbox" checked=model.enabled}}</span>
|
||||
{{#unless model.changed}}
|
||||
<a class='preview-link' href={{previewUrl}} target='_blank' title="{{i18n 'admin.customize.explain_preview'}}">{{i18n 'admin.customize.preview'}}</a>
|
||||
|
|
||||
<a href={{undoPreviewUrl}} target='_blank' title="{{i18n 'admin.customize.explain_undo_preview'}}">{{i18n 'admin.customize.undo_preview'}}</a>
|
||||
|
|
||||
<a href={{defaultStyleUrl}} target='_blank' title="{{i18n 'admin.customize.explain_rescue_preview'}}">{{i18n 'admin.customize.rescue_preview'}}</a><br>
|
||||
{{/unless}}
|
||||
</div>
|
||||
|
||||
<div class='buttons'>
|
||||
{{#d-button action="save" disabled=saveDisabled class='btn-primary'}}
|
||||
{{saveButtonText}}
|
||||
{{/d-button}}
|
||||
{{d-button action="destroy" label="admin.customize.delete" icon="trash" class="btn-danger"}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1,13 +0,0 @@
|
||||
<div class='content-list span6'>
|
||||
<h3>{{i18n 'admin.customize.css_html.long_title'}}</h3>
|
||||
<ul>
|
||||
{{#each model as |c|}}
|
||||
{{customize-link customization=c}}
|
||||
{{/each}}
|
||||
</ul>
|
||||
|
||||
{{d-button label="admin.customize.new" icon="plus" action="newCustomization" class="btn-primary"}}
|
||||
{{d-button action="importModal" icon="upload" label="admin.customize.import"}}
|
||||
</div>
|
||||
|
||||
{{outlet}}
|
||||
@ -0,0 +1,78 @@
|
||||
<div class="current-style {{if maximized 'maximized'}}">
|
||||
<div class='wrapper'>
|
||||
<h2>{{i18n 'admin.customize.theme.edit_css_html'}} {{#link-to 'adminCustomizeThemes.show' model.id replace=true}}{{model.name}}{{/link-to}}</h2>
|
||||
|
||||
{{#if error}}
|
||||
<pre class='field-error'>{{error}}</pre>
|
||||
{{/if}}
|
||||
|
||||
<div class='edit-main-nav'>
|
||||
<ul class='nav nav-pills target'>
|
||||
{{#if showCommon}}
|
||||
<li>
|
||||
{{#link-to 'adminCustomizeThemes.edit' model.id 'common' fieldName replace=true title=field.title}}
|
||||
{{i18n 'admin.customize.theme.common'}}
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if showDesktop}}
|
||||
<li>
|
||||
{{#link-to 'adminCustomizeThemes.edit' model.id 'desktop' fieldName replace=true title=field.title}}
|
||||
{{i18n 'admin.customize.theme.desktop'}}
|
||||
{{fa-icon 'desktop'}}
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if showMobile}}
|
||||
<li class='mobile'>
|
||||
{{#link-to 'adminCustomizeThemes.edit' model.id 'mobile' fieldName replace=true title=field.title}}
|
||||
{{i18n 'admin.customize.theme.mobile'}}
|
||||
{{fa-icon 'mobile'}}
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{/if}}
|
||||
</ul>
|
||||
<div class='show-overidden'>
|
||||
<label>
|
||||
{{input type="checkbox" checked=onlyOverridden}}
|
||||
{{i18n 'admin.site_settings.show_overriden'}}
|
||||
</label>
|
||||
</div>
|
||||
<div class='clearfix'></div>
|
||||
</div>
|
||||
|
||||
<div class='admin-controls'>
|
||||
<ul class='nav nav-pills fields'>
|
||||
{{#each fields as |field|}}
|
||||
<li>
|
||||
{{#link-to 'adminCustomizeThemes.edit' model.id currentTargetName field.name replace=true title=field.title}}
|
||||
{{#if field.icon}}{{fa-icon field.icon}} {{/if}}
|
||||
{{i18n field.key}}
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{/each}}
|
||||
<li class='toggle-maximize'>
|
||||
<a {{action "toggleMaximize"}}>
|
||||
<i class="fa fa-{{maximizeIcon}}"></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{{ace-editor content=activeSection editorId=editorId mode=activeSectionMode autofocus="true"}}
|
||||
|
||||
<div class='admin-footer'>
|
||||
<div class='status-actions'>
|
||||
{{#unless model.changed}}
|
||||
<a class='preview-link' href={{previewUrl}} target='_blank' title="{{i18n 'admin.customize.explain_preview'}}">{{i18n 'admin.customize.preview'}}</a>
|
||||
{{/unless}}
|
||||
</div>
|
||||
|
||||
<div class='buttons'>
|
||||
{{#d-button action="save" disabled=saveDisabled class='btn-primary'}}
|
||||
{{saveButtonText}}
|
||||
{{/d-button}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
140
app/assets/javascripts/admin/templates/customize-themes-show.hbs
Normal file
140
app/assets/javascripts/admin/templates/customize-themes-show.hbs
Normal file
@ -0,0 +1,140 @@
|
||||
<div class="show-current-style">
|
||||
<h2>
|
||||
{{#if editingName}}
|
||||
{{text-field value=model.name autofocus="true"}}
|
||||
{{d-button action="finishedEditingName" class="btn-primary btn-small submit-edit" icon="check"}}
|
||||
{{d-button action="cancelEditingName" class="btn-small cancel-edit" icon="times"}}
|
||||
{{else}}
|
||||
{{model.name}} <a {{action "startEditingName"}}>{{fa-icon "pencil"}}</a>
|
||||
{{/if}}
|
||||
</h2>
|
||||
|
||||
{{#if model.remote_theme}}
|
||||
<p>
|
||||
<a href="{{model.remote_theme.about_url}}">{{i18n "admin.customize.theme.about_theme"}}</a>
|
||||
</p>
|
||||
{{#if model.remote_theme.license_url}}
|
||||
<p>
|
||||
<a href="{{model.remote_theme.license_url}}">{{i18n "admin.customize.theme.license"}} {{fa-icon "copyright"}}</a>
|
||||
</p>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
|
||||
{{#if parentThemes}}
|
||||
<h3>{{i18n "admin.customize.theme.component_of"}}</h3>
|
||||
<ul>
|
||||
{{#each parentThemes as |theme|}}
|
||||
<li>{{#link-to 'adminCustomizeThemes.show' theme replace=true}}{{theme.name}}{{/link-to}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{else}}
|
||||
<p>
|
||||
{{inline-edit-checkbox action="applyDefault" labelKey="admin.customize.theme.is_default" checked=model.default}}
|
||||
{{inline-edit-checkbox action="applyUserSelectable" labelKey="admin.customize.theme.user_selectable" checked=model.user_selectable}}
|
||||
</p>
|
||||
|
||||
<h3>{{i18n "admin.customize.theme.color_scheme"}}</h3>
|
||||
<p>{{i18n "admin.customize.theme.color_scheme_select"}}</p>
|
||||
<p>{{combo-box content=colorSchemes
|
||||
nameProperty="name"
|
||||
value=colorSchemeId
|
||||
selectionIcon="paint-brush"
|
||||
valueAttribute="id"}}
|
||||
{{#if colorSchemeChanged}}
|
||||
{{d-button action="changeScheme" class="btn-primary btn-small submit-edit" icon="check"}}
|
||||
{{d-button action="cancelChangeScheme" class="btn-small cancel-edit" icon="times"}}
|
||||
{{/if}}
|
||||
</p>
|
||||
{{#link-to 'adminCustomize.colors' class="btn edit"}}{{i18n 'admin.customize.colors.edit'}}{{/link-to}}
|
||||
{{/if}}
|
||||
|
||||
<h3>{{i18n "admin.customize.theme.css_html"}}</h3>
|
||||
{{#if hasEditedFields}}
|
||||
<p>{{i18n "admin.customize.theme.custom_sections"}}</p>
|
||||
<ul>
|
||||
{{#each editedDescriptions as |desc|}}
|
||||
<li>{{desc}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{else}}
|
||||
<p>
|
||||
{{i18n "admin.customize.theme.edit_css_html_help"}}
|
||||
</p>
|
||||
{{/if}}
|
||||
<p>
|
||||
{{#if model.remote_theme}}
|
||||
{{#if model.remote_theme.commits_behind}}
|
||||
{{#d-button action="updateToLatest" icon="download" class='btn-primary'}}{{i18n "admin.customize.theme.update_to_latest"}}{{/d-button}}
|
||||
{{else}}
|
||||
{{#d-button action="checkForThemeUpdates" icon="refresh"}}{{i18n "admin.customize.theme.check_for_updates"}}{{/d-button}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{#d-button action="editTheme" class="btn edit"}}{{i18n 'admin.customize.theme.edit_css_html'}}{{/d-button}}
|
||||
{{#if model.remote_theme}}
|
||||
<span class='status-message'>
|
||||
{{#if updatingRemote}}
|
||||
{{i18n 'admin.customize.theme.updating'}}
|
||||
{{else}}
|
||||
{{#if model.remote_theme.commits_behind}}
|
||||
{{i18n 'admin.customize.theme.commits_behind' count=model.remote_theme.commits_behind}}
|
||||
{{else}}
|
||||
{{i18n 'admin.customize.theme.up_to_date'}} {{format-date model.remote_theme.updated_at leaveAgo="true"}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</span>
|
||||
{{/if}}
|
||||
</p>
|
||||
|
||||
|
||||
<h3>{{i18n "admin.customize.theme.uploads"}}</h3>
|
||||
{{#if model.uploads}}
|
||||
<ul class='removable-list'>
|
||||
{{#each model.uploads as |upload|}}
|
||||
<li>
|
||||
<span class='col'>${{upload.name}}: <a href={{upload.url}} target='_blank'>{{upload.filename}}</a></span>
|
||||
<span class='col'>
|
||||
{{d-button action="removeUpload" actionParam=upload class="second btn-small cancel-edit" icon="times"}}
|
||||
</span>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{else}}
|
||||
<p>{{i18n "admin.customize.theme.no_uploads"}}</p>
|
||||
{{/if}}
|
||||
<p>
|
||||
{{#d-button action="addUploadModal" icon="plus"}}{{i18n "admin.customize.theme.add"}}{{/d-button}}
|
||||
</p>
|
||||
|
||||
{{#if availableChildThemes}}
|
||||
<h3>{{i18n "admin.customize.theme.theme_components"}}</h3>
|
||||
{{#unless model.childThemes.length}}
|
||||
<p>
|
||||
<label class='checkbox-label'>
|
||||
{{input type="checkbox" checked=allowChildThemes}}
|
||||
{{i18n "admin.customize.theme.child_themes_check"}}
|
||||
</label>
|
||||
</p>
|
||||
{{else}}
|
||||
<ul class='removable-list'>
|
||||
{{#each model.childThemes as |child|}}
|
||||
<li>{{#link-to 'adminCustomizeThemes.show' child replace=true class='col'}}{{child.name}}{{/link-to}} {{d-button action="removeChildTheme" actionParam=child class="btn-small cancel-edit col" icon="times"}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{/unless}}
|
||||
{{#if selectableChildThemes}}
|
||||
<p>{{combo-box content=selectableChildThemes
|
||||
nameProperty="name"
|
||||
value=selectedChildThemeId
|
||||
valueAttribute="id"}}
|
||||
|
||||
{{#d-button action="addChildTheme" icon="plus"}}{{i18n "admin.customize.theme.add"}}{{/d-button}}
|
||||
</p>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
<a href='{{previewUrl}}' title="{{i18n 'admin.customize.explain_preview'}}" target='_blank' class='btn'>{{fa-icon 'desktop'}}{{i18n 'admin.customize.theme.preview'}}</a>
|
||||
<a class="btn export" target="_blank" href={{downloadUrl}}>{{fa-icon "download"}} {{i18n 'admin.export_json.button_text'}}</a>
|
||||
|
||||
{{d-button action="destroy" label="admin.customize.delete" icon="trash" class="btn-danger"}}
|
||||
</div>
|
||||
24
app/assets/javascripts/admin/templates/customize-themes.hbs
Normal file
24
app/assets/javascripts/admin/templates/customize-themes.hbs
Normal file
@ -0,0 +1,24 @@
|
||||
{{#unless editingTheme}}
|
||||
<div class='content-list span6'>
|
||||
<h3>{{i18n 'admin.customize.theme.long_title'}}</h3>
|
||||
<ul>
|
||||
{{#each sortedThemes as |theme|}}
|
||||
<li>
|
||||
{{#link-to 'adminCustomizeThemes.show' theme replace=true}}
|
||||
{{theme.name}}
|
||||
{{#if theme.user_selectable}}
|
||||
{{fa-icon "user"}}
|
||||
{{/if}}
|
||||
{{#if theme.default}}
|
||||
{{fa-icon "asterisk"}}
|
||||
{{/if}}
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
|
||||
{{d-button label="admin.customize.new" icon="plus" action="newTheme" class="btn-primary"}}
|
||||
{{d-button action="importModal" icon="upload" label="admin.customize.import"}}
|
||||
</div>
|
||||
{{/unless}}
|
||||
{{outlet}}
|
||||
@ -1,7 +1,7 @@
|
||||
<div class='customize'>
|
||||
{{#admin-nav}}
|
||||
{{nav-item route='adminCustomizeThemes' label='admin.customize.theme.title'}}
|
||||
{{nav-item route='adminCustomize.colors' label='admin.customize.colors.title'}}
|
||||
{{nav-item route='adminCustomizeCssHtml' label='admin.customize.css_html.title'}}
|
||||
{{nav-item route='adminSiteText' label='admin.site_text.title'}}
|
||||
{{nav-item route='adminCustomizeEmailTemplates' label='admin.customize.email_templates.title'}}
|
||||
{{nav-item route='adminUserFields' label='admin.user_fields.title'}}
|
||||
|
||||
@ -261,8 +261,8 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="title">{{top_referrers.title}} ({{i18n 'admin.dashboard.reports.last_30_days'}})</th>
|
||||
<th>{{number top_referrers.ytitles.num_clicks}}</th>
|
||||
<th>{{number top_referrers.ytitles.num_topics}}</th>
|
||||
<th>{{top_referrers.ytitles.num_clicks}}</th>
|
||||
<th>{{top_referrers.ytitles.num_topics}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{{#each top_referrers.data as |r|}}
|
||||
|
||||
@ -28,7 +28,7 @@
|
||||
{{/if}}
|
||||
</td>
|
||||
<td><a href='mailto:{{unbound l.to_address}}'>{{l.to_address}}</a></td>
|
||||
<td>{{l.email_type}}</td>
|
||||
<td><a {{action "showIncomingEmail" l.id}}>{{l.email_type}}</a></td>
|
||||
</tr>
|
||||
{{else}}
|
||||
<tr><td colspan="4">{{i18n 'admin.email.logs.none'}}</td></tr>
|
||||
|
||||
@ -43,7 +43,7 @@
|
||||
{{#if htmlEmpty}}
|
||||
<p>{{i18n 'admin.email.no_result'}}</p>
|
||||
{{else}}
|
||||
<iframe src="{{iframeSrc}}" />
|
||||
<iframe srcdoc={{model.html_content}} />
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<pre>{{{model.text_content}}}</pre>
|
||||
|
||||
@ -2,9 +2,10 @@
|
||||
{{#if embedding.embeddable_hosts}}
|
||||
<table class='embedding'>
|
||||
<tr>
|
||||
<th style='width: 30%'>{{i18n "admin.embedding.host"}}</th>
|
||||
<th style='width: 30%'>{{i18n "admin.embedding.path_whitelist"}}</th>
|
||||
<th style='width: 30%'>{{i18n "admin.embedding.category"}}</th>
|
||||
<th style='width: 25%'>{{i18n "admin.embedding.host"}}</th>
|
||||
<th style='width: 15%'>{{i18n "admin.embedding.class_name"}}</th>
|
||||
<th style='width: 25%'>{{i18n "admin.embedding.path_whitelist"}}</th>
|
||||
<th style='width: 25%'>{{i18n "admin.embedding.category"}}</th>
|
||||
<th style='width: 10%'> </th>
|
||||
</tr>
|
||||
{{#each embedding.embeddable_hosts as |host|}}
|
||||
@ -43,6 +44,7 @@
|
||||
|
||||
{{embedding-setting field="feed_polling_enabled" value=embedding.feed_polling_enabled type="checkbox"}}
|
||||
{{embedding-setting field="feed_polling_url" value=embedding.feed_polling_url}}
|
||||
{{embedding-setting field="feed_polling_frequency_mins" value=embedding.feed_polling_frequency_mins}}
|
||||
{{embedding-setting field="embed_username_key_from_feed" value=embedding.embed_username_key_from_feed}}
|
||||
</div>
|
||||
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
<table class="admin-flags">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class='user'></th>
|
||||
<th class='excerpt'></th>
|
||||
<th class='flaggers'>{{i18n 'admin.flags.flagged_by'}}</th>
|
||||
<th class='flaggers'>{{#if adminOldFlagsView}}{{i18n 'admin.flags.resolved_by'}}{{/if}}</th>
|
||||
@ -13,30 +12,42 @@
|
||||
{{#each content as |flaggedPost|}}
|
||||
<tr class={{flaggedPost.extraClasses}}>
|
||||
|
||||
<td class='user'>
|
||||
{{#if flaggedPost.postAuthorFlagged}}
|
||||
{{#if flaggedPost.user}}
|
||||
{{#link-to 'adminUser' flaggedPost.user}}{{avatar flaggedPost.user imageSize="large"}}{{/link-to}}
|
||||
{{#if flaggedPost.wasEdited}}<i class="fa fa-pencil" title="{{i18n 'admin.flags.was_edited'}}"></i>{{/if}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{#if adminActiveFlagsView}}
|
||||
{{#if flaggedPost.previous_flags_count}}
|
||||
<span title="{{i18n 'admin.flags.previous_flags_count' count=flaggedPost.previous_flags_count}}" class="badge-notification flagged-posts">{{flaggedPost.previous_flags_count}}</span>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</td>
|
||||
|
||||
<td class='excerpt'>
|
||||
<h3>
|
||||
{{#if flaggedPost.topic.isPrivateMessage}}
|
||||
<span class="private-message-glyph">{{fa-icon "envelope"}}</span>
|
||||
|
||||
<div class="flex-center-align">
|
||||
<div class="flagged-post-avatar">
|
||||
{{#if flaggedPost.postAuthorFlagged}}
|
||||
{{#if flaggedPost.user}}
|
||||
{{#link-to 'adminUser' flaggedPost.user}}{{avatar flaggedPost.user imageSize="large"}}{{/link-to}}
|
||||
{{#if flaggedPost.wasEdited}}<i class="fa fa-pencil" title="{{i18n 'admin.flags.was_edited'}}"></i>{{/if}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{#if adminActiveFlagsView}}
|
||||
{{#if flaggedPost.previous_flags_count}}
|
||||
<span title="{{i18n 'admin.flags.previous_flags_count' count=flaggedPost.previous_flags_count}}" class="badge-notification flagged-posts">{{flaggedPost.previous_flags_count}}</span>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="topic-excerpt">
|
||||
<h3>
|
||||
{{#if flaggedPost.topic.isPrivateMessage}}
|
||||
<span class="private-message-glyph">{{fa-icon "envelope"}}</span>
|
||||
{{/if}}
|
||||
{{topic-status topic=flaggedPost.topic}}
|
||||
<a href='{{unbound flaggedPost.url}}'>{{{unbound flaggedPost.topic.fancyTitle}}}</a>
|
||||
</h3>
|
||||
{{#unless site.mobileView}}
|
||||
{{#if flaggedPost.postAuthorFlagged}}
|
||||
<p>{{{flaggedPost.excerpt}}}</p>
|
||||
{{/if}}
|
||||
{{/unless}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if site.mobileView}}
|
||||
{{#if flaggedPost.postAuthorFlagged}}
|
||||
<p>{{{flaggedPost.excerpt}}}</p>
|
||||
{{/if}}
|
||||
{{topic-status topic=flaggedPost.topic}}
|
||||
<a href='{{unbound flaggedPost.url}}'>{{{unbound flaggedPost.topic.fancyTitle}}}</a>
|
||||
</h3>
|
||||
{{#if flaggedPost.postAuthorFlagged}}
|
||||
<p>{{{flaggedPost.excerpt}}}</p>
|
||||
{{/if}}
|
||||
</td>
|
||||
|
||||
@ -104,7 +115,6 @@
|
||||
|
||||
{{#each flaggedPost.conversations as |c|}}
|
||||
<tr class='message'>
|
||||
<td></td>
|
||||
<td colspan="3">
|
||||
<div>
|
||||
{{#if c.response}}
|
||||
@ -130,7 +140,7 @@
|
||||
|
||||
{{#unless adminOldFlagsView}}
|
||||
<tr>
|
||||
<td colspan="4" class="action">
|
||||
<td colspan="3" class="action">
|
||||
{{#if adminActiveFlagsView}}
|
||||
<button title='{{i18n 'admin.flags.agree_title'}}' class='btn' {{action "showAgreeFlagModal" flaggedPost}}><i class="fa fa-thumbs-o-up"></i>{{i18n 'admin.flags.agree'}}…</button>
|
||||
{{#if flaggedPost.postHidden}}
|
||||
|
||||
@ -83,6 +83,12 @@
|
||||
{{combo-box name="alias" valueAttribute="value" value=model.alias_level content=aliasLevelOptions}}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label>{{i18n 'groups.notification_level'}}</label>
|
||||
{{notifications-button i18nPrefix='groups.notifications' notificationLevel=model.default_notification_level}}
|
||||
<div class='clearfix'></div>
|
||||
</div>
|
||||
|
||||
{{#unless model.automatic}}
|
||||
<div>
|
||||
<label for="automatic_membership">{{i18n 'admin.groups.automatic_membership_email_domains'}}</label>
|
||||
|
||||
@ -1 +1,11 @@
|
||||
<p>{{i18n "admin.groups.bulk_complete"}}</p>
|
||||
{{#if bulkAddResponse}}
|
||||
<p>{{{bulkAddResponse.message}}}</p>
|
||||
{{#if bulkAddResponse.users_not_added}}
|
||||
<p>{{i18n "admin.groups.bulk_complete_users_not_added"}}</p>
|
||||
{{#each bulkAddResponse.users_not_added as |user|}}
|
||||
{{user}}<br/>
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<p>{{i18n "admin.groups.bulk_complete"}}</p>
|
||||
{{/if}}
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
<div class="groups-type-index">
|
||||
<p>{{i18n messageKey}}</p>
|
||||
|
||||
<div>
|
||||
{{#link-to 'adminGroup' 'new' class="btn"}}
|
||||
{{fa-icon "plus"}} {{i18n 'admin.groups.new'}}
|
||||
{{/link-to}}
|
||||
</div>
|
||||
</div>
|
||||
@ -1,29 +1,31 @@
|
||||
<div class='row groups'>
|
||||
<div class='content-list span6'>
|
||||
<h3>{{i18n 'admin.groups.edit'}}</h3>
|
||||
<ul>
|
||||
{{#each sortedGroups as |group|}}
|
||||
<li>
|
||||
{{#link-to "adminGroup" group.type group.name}}{{group.name}}
|
||||
{{#if group.userCountDisplay}}
|
||||
<span class="count">{{number group.userCountDisplay}}</span>
|
||||
{{/if}}
|
||||
{{#if sortedGroups}}
|
||||
<div class='content-list span6'>
|
||||
<h3>{{i18n 'admin.groups.edit'}}</h3>
|
||||
<ul>
|
||||
{{#each sortedGroups as |group|}}
|
||||
<li>
|
||||
{{#link-to "adminGroup" group.type group.name}}{{group.name}}
|
||||
{{#if group.userCountDisplay}}
|
||||
<span class="count">{{number group.userCountDisplay}}</span>
|
||||
{{/if}}
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
<div class='controls'>
|
||||
{{#if isAuto}}
|
||||
{{d-button action="refreshAutoGroups" icon="refresh" label="admin.groups.refresh" disabled=refreshingAutoGroups}}
|
||||
{{else}}
|
||||
{{#link-to 'adminGroup' 'new' class="btn"}}
|
||||
{{fa-icon "plus"}} {{i18n 'admin.groups.new'}}
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
<div class='controls'>
|
||||
{{#if isAuto}}
|
||||
{{d-button action="refreshAutoGroups" icon="refresh" label="admin.groups.refresh" disabled=refreshingAutoGroups}}
|
||||
{{else}}
|
||||
{{#link-to 'adminGroup' 'new' class="btn"}}
|
||||
{{fa-icon "plus"}} {{i18n 'admin.groups.new'}}
|
||||
{{/link-to}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class='content-editor'>
|
||||
<div class="span13">
|
||||
{{outlet}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,41 +1,43 @@
|
||||
<div class="staff-action-logs-controls">
|
||||
<a {{action "clearAllFilters"}} class="clear-filters filter {{unless filtersExists 'invisible'}}">
|
||||
<span class="label">{{i18n 'admin.logs.staff_actions.clear_filters'}}</span>
|
||||
</a>
|
||||
{{#if actionFilter}}
|
||||
<a {{action "clearFilter" "actionFilter"}} class="filter">
|
||||
<span class="label">{{i18n 'admin.logs.action'}}</span>: {{actionFilter}}
|
||||
{{fa-icon "times-circle"}}
|
||||
{{#if filtersExists}}
|
||||
<div>
|
||||
<a {{action "clearAllFilters"}} class="clear-filters filter">
|
||||
<span class="label">{{i18n 'admin.logs.staff_actions.clear_filters'}}</span>
|
||||
</a>
|
||||
{{#if actionFilter}}
|
||||
<a {{action "clearFilter" "actionFilter"}} class="filter">
|
||||
<span class="label">{{i18n 'admin.logs.action'}}</span>: {{actionFilter}}
|
||||
{{fa-icon "times-circle"}}
|
||||
</a>
|
||||
{{/if}}
|
||||
{{#if filters.acting_user}}
|
||||
<a {{action "clearFilter" "acting_user"}} class="filter">
|
||||
<span class="label">{{i18n 'admin.logs.staff_actions.staff_user'}}</span>: {{filters.acting_user}}
|
||||
{{fa-icon "times-circle"}}
|
||||
</a>
|
||||
{{/if}}
|
||||
{{#if filters.target_user}}
|
||||
<a {{action "clearFilter" "target_user"}} class="filter">
|
||||
<span class="label">{{i18n 'admin.logs.staff_actions.target_user'}}</span>: {{filters.target_user}}
|
||||
{{fa-icon "times-circle"}}
|
||||
</a>
|
||||
{{/if}}
|
||||
{{#if filters.subject}}
|
||||
<a {{action "clearFilter" "subject"}} class="filter">
|
||||
<span class="label">{{i18n 'admin.logs.staff_actions.subject'}}</span>: {{filters.subject}}
|
||||
{{fa-icon "times-circle"}}
|
||||
</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{else}}
|
||||
{{i18n "admin.logs.staff_actions.filter"}} {{combo-box content=userHistoryActions nameProperty="name" value=filterActionId none="admin.logs.staff_actions.all"}}
|
||||
{{/if}}
|
||||
{{#if filters.acting_user}}
|
||||
<a {{action "clearFilter" "acting_user"}} class="filter">
|
||||
<span class="label">{{i18n 'admin.logs.staff_actions.staff_user'}}</span>: {{filters.acting_user}}
|
||||
{{fa-icon "times-circle"}}
|
||||
</a>
|
||||
{{/if}}
|
||||
{{#if filters.target_user}}
|
||||
<a {{action "clearFilter" "target_user"}} class="filter">
|
||||
<span class="label">{{i18n 'admin.logs.staff_actions.target_user'}}</span>: {{filters.target_user}}
|
||||
{{fa-icon "times-circle"}}
|
||||
</a>
|
||||
{{/if}}
|
||||
{{#if filters.subject}}
|
||||
<a {{action "clearFilter" "subject"}} class="filter">
|
||||
<span class="label">{{i18n 'admin.logs.staff_actions.subject'}}</span>: {{filters.subject}}
|
||||
{{fa-icon "times-circle"}}
|
||||
</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="pull-right">
|
||||
{{d-button action="exportStaffActionLogs" label="admin.export_csv.button_text" icon="download"}}
|
||||
</div>
|
||||
<br>
|
||||
|
||||
<div class="staff-action-logs-instructions {{unless showInstructions 'invisible'}}">
|
||||
{{i18n 'admin.logs.staff_actions.instructions'}}
|
||||
<div class="pull-right">
|
||||
{{d-button action="exportStaffActionLogs" label="admin.export_csv.button_text" icon="download"}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<div class='table staff-actions'>
|
||||
<div class="heading-container">
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user