Merge master

This commit is contained in:
Neil Lalonde 2015-02-19 16:17:51 -05:00
commit 16bea87fd3
6463 changed files with 84842 additions and 99584 deletions

View File

@ -49,6 +49,7 @@
"notEqual",
"require",
"requirejs",
"hasModule",
"Blob",
"File"],
"node" : false,

31
Gemfile
View File

@ -67,17 +67,19 @@ unless Bundler::Dependency::PLATFORM_MAP.include? :mri_21
end
end
gem 'seed-fu', '~> 2.3.3'
if rails_master?
gem 'arel', git: 'https://github.com/rails/arel.git'
gem 'rails', git: 'https://github.com/rails/rails.git'
gem 'rails-observers', git: 'https://github.com/SamSaffron/rails-observers.git'
gem 'seed-fu', git: 'https://github.com/SamSaffron/seed-fu.git', branch: 'discourse'
else
gem 'seed-fu', '~> 2.3.3'
gem 'rails'
gem 'rails-observers'
end
gem 'actionpack-action_caching'
gem 'rails-observers'
# Rails 4.1.6+ will relax the mail gem version requirement to `~> 2.5, >= 2.5.4`.
# However, mail gem 2.6.x currently does not work with discourse because of the
@ -102,9 +104,10 @@ end
gem 'onebox'
gem 'ember-rails'
gem 'ember-source', '1.6.0.beta.2'
gem 'handlebars-source', '1.3.0'
gem 'ember-source', '1.9.0.beta.4'
gem 'handlebars-source', '2.0.0'
gem 'barber'
gem '6to5'
gem 'message_bus'
gem 'rails_multisite', path: 'vendor/gems/rails_multisite'
@ -114,15 +117,13 @@ gem 'eventmachine'
gem 'fast_xs'
gem 'fast_xor'
gem 'fastimage'
gem 'fog', '1.22.1', require: false
# while we sort out https://github.com/sdsykes/fastimage/pull/46
gem 'fastimage_discourse', require: 'fastimage'
gem 'fog', '1.26.0', require: false
gem 'unf', require: false
# see: https://twitter.com/samsaffron/status/412360162297393152
# Massive amount of changes made in branch we use, no PR upstreamed
# We need to get this sorted
# https://github.com/samsaffron/email_reply_parser
gem 'email_reply_parser-discourse', require: 'email_reply_parser'
gem 'email_reply_parser'
# note: for image_optim to correctly work you need
# sudo apt-get install -y advancecomp gifsicle jpegoptim libjpeg-progs optipng pngcrush
@ -144,8 +145,7 @@ gem 'omniauth-github-discourse', require: 'omniauth-github'
gem 'omniauth-oauth2', require: false
gem 'omniauth-google-oauth2'
gem 'oj'
# while resolving https://groups.google.com/forum/#!topic/ruby-pg/5_ylGmog1S4
gem 'pg', '0.15.1'
gem 'pg'
gem 'pry-rails', require: false
gem 'rake'
@ -225,7 +225,7 @@ gem 'lru_redux'
gem 'htmlentities', require: false
# IMPORTANT: mini profiler monkey patches, so it better be required last
# If you want to amend mini profiler to do the monkey patches in the railstie
# If you want to amend mini profiler to do the monkey patches in the railties
# we are open to it. by deferring require to the initializer we can configure discourse installs without it
gem 'flamegraph', require: false
@ -240,6 +240,9 @@ gem 'rbtrace', require: false, platform: :mri
gem 'ruby-readability', require: false
gem 'simple-rss', require: false
# TODO mri_22 should be here, but bundler was real slow to pick it up
# not even in production bundler yet, monkey patching it in feels bad
gem 'gctools', require: false, platform: :mri_21
gem 'stackprof', require: false, platform: :mri_21
gem 'memory_profiler', require: false, platform: :mri_21

View File

@ -6,6 +6,11 @@ PATH
GEM
remote: https://rubygems.org/
specs:
6to5 (0.5.0)
6to5-source (>= 1.14, < 4)
execjs (~> 2.0)
6to5-source (3.3.7)
CFPropertyList (2.2.8)
actionmailer (4.1.8)
actionpack (= 4.1.8)
actionview (= 4.1.8)
@ -36,27 +41,26 @@ GEM
minitest (~> 5.1)
thread_safe (~> 0.1)
tzinfo (~> 1.1)
addressable (2.3.6)
annotate (2.6.5)
activerecord (>= 2.3.0)
rake (>= 0.8.7)
arel (5.0.1.20140414130214)
barber (0.4.2)
barber (0.5.0)
ember-source
execjs
handlebars-source
better_errors (2.0.0)
handlebars-source (>= 1.0.0.rc.4)
better_errors (2.1.1)
coderay (>= 1.0.0)
erubis (>= 2.6.6)
rack (>= 0.9.0)
binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1)
builder (3.2.2)
celluloid (0.15.2)
timers (~> 1.1.0)
celluloid (0.16.0)
timers (~> 4.0.0)
certified (1.0.0)
coderay (1.1.0)
connection_pool (2.0.0)
connection_pool (2.1.1)
crass (0.2.1)
daemons (1.1.9)
debug_inspector (0.0.2)
@ -65,7 +69,7 @@ GEM
dotenv (0.11.1)
dotenv-deployment (~> 0.0.2)
dotenv-deployment (0.0.2)
email_reply_parser-discourse (0.6)
email_reply_parser (0.5.8)
ember-data-source (0.14)
ember-source
ember-rails (0.14.1)
@ -77,12 +81,12 @@ GEM
handlebars-source
jquery-rails (>= 1.0.17)
railties (>= 3.1)
ember-source (1.6.0.beta.2)
handlebars-source (~> 1.0)
ember-source (1.9.0.beta.4)
handlebars-source (~> 2.0)
erubis (2.7.0)
eventmachine (1.0.3)
excon (0.39.6)
execjs (2.2.1)
eventmachine (1.0.7)
excon (0.44.2)
execjs (2.2.2)
exifr (1.1.3)
fabrication (2.9.8)
fakeweb (1.3.0)
@ -96,30 +100,77 @@ GEM
rake
rake-compiler
fast_xs (0.8.0)
fastimage (1.6.3)
addressable (~> 2.3, >= 2.3.5)
ffi (1.9.5)
flamegraph (0.0.8)
fastimage_discourse (1.6.6)
ffi (1.9.6)
fission (0.5.0)
CFPropertyList (~> 2.2)
flamegraph (0.1.0)
fast_stack
fog (1.22.1)
fog-brightbox
fog-core (~> 1.22)
fog (1.26.0)
fog-atmos
fog-brightbox (~> 0.4)
fog-core (~> 1.27, >= 1.27.1)
fog-ecloud
fog-json
fog-profitbricks
fog-radosgw (>= 0.0.2)
fog-sakuracloud (>= 0.0.4)
fog-softlayer
fog-storm_on_demand
fog-terremark
fog-vmfusion
fog-voxel
fog-xml (~> 0.1.1)
ipaddress (~> 0.5)
nokogiri (~> 1.5, >= 1.5.11)
fog-brightbox (0.5.1)
fog-atmos (0.1.0)
fog-core
fog-xml
fog-brightbox (0.7.1)
fog-core (~> 1.22)
fog-json
inflecto
fog-core (1.24.0)
inflecto (~> 0.0.2)
fog-core (1.27.2)
builder
excon (~> 0.38)
formatador (~> 0.2)
mime-types
net-scp (~> 1.1)
net-ssh (>= 2.1.3)
fog-ecloud (0.0.2)
fog-core
fog-xml
fog-json (1.0.0)
multi_json (~> 1.0)
fog-profitbricks (0.0.1)
fog-core
fog-xml
nokogiri
fog-radosgw (0.0.3)
fog-core (>= 1.21.0)
fog-json
fog-xml (>= 0.0.1)
fog-sakuracloud (0.1.1)
fog-core
fog-json
fog-softlayer (0.3.26)
fog-core
fog-json
fog-storm_on_demand (0.1.0)
fog-core
fog-json
fog-terremark (0.0.3)
fog-core
fog-xml
fog-vmfusion (0.0.1)
fission
fog-core
fog-voxel (0.0.2)
fog-core
fog-xml
fog-xml (0.1.1)
fog-core
nokogiri (~> 1.5, >= 1.5.11)
foreman (0.75.0)
dotenv (~> 0.11.1)
thor (~> 0.19.1)
@ -129,12 +180,13 @@ GEM
given_core (3.5.4)
sorcerer (>= 0.3.7)
guess_html_encoding (0.0.9)
handlebars-source (1.3.0)
hashie (3.3.1)
handlebars-source (2.0.0)
hashie (3.3.2)
highline (1.6.21)
hike (1.2.3)
hiredis (0.5.2)
htmlentities (4.3.2)
hiredis (0.6.0)
hitimes (1.2.2)
htmlentities (4.3.3)
i18n (0.6.11)
image_optim (0.9.1)
exifr (~> 1.1.3)
@ -149,7 +201,7 @@ GEM
jquery-rails (3.1.2)
railties (>= 3.0, < 5.0)
thor (>= 0.14, < 2.0)
json (1.8.1)
json (1.8.2)
jwt (1.0.0)
kgio (2.9.2)
librarian (0.1.2)
@ -163,20 +215,20 @@ GEM
mime-types (~> 1.16)
treetop (~> 1.4.8)
memory_profiler (0.0.4)
message_bus (1.0.5)
message_bus (1.0.6)
eventmachine
rack (>= 1.1.3)
redis
metaclass (0.0.4)
method_source (0.8.2)
mime-types (1.25.1)
mini_portile (0.6.1)
minitest (5.4.2)
mini_portile (0.6.2)
minitest (5.5.1)
mocha (1.1.0)
metaclass (~> 0.0.1)
mock_redis (0.13.2)
mock_redis (0.14.0)
moneta (0.8.0)
msgpack (0.5.8)
msgpack (0.5.11)
multi_json (1.10.1)
multi_xml (0.5.5)
multipart-post (2.0.0)
@ -185,7 +237,7 @@ GEM
net-ssh (>= 2.6.5)
net-ssh (2.9.1)
netrc (0.7.7)
nokogiri (1.6.4.1)
nokogiri (1.6.6.2)
mini_portile (~> 0.6.0)
nokogumbo (1.1.12)
nokogiri
@ -196,7 +248,7 @@ GEM
multi_json (~> 1.3)
multi_xml (~> 0.5)
rack (~> 1.2)
oj (2.10.2)
oj (2.11.4)
omniauth (1.2.2)
hashie (>= 1.2, < 4)
rack (~> 1.0)
@ -222,7 +274,7 @@ GEM
omniauth-twitter (1.0.1)
multi_json (~> 1.3)
omniauth-oauth (~> 1.0)
onebox (1.5.7)
onebox (1.5.12)
moneta (~> 0.7)
multi_json (~> 1.7)
mustache (~> 0.99)
@ -230,7 +282,7 @@ GEM
openid-redis-store (0.0.2)
redis
ruby-openid
pg (0.15.1)
pg (0.18.1)
polyglot (0.3.5)
progress (3.0.1)
pry (0.10.1)
@ -239,9 +291,9 @@ GEM
slop (~> 3.4)
pry-nav (0.2.4)
pry (>= 0.9.10, < 0.11.0)
pry-rails (0.3.2)
pry-rails (0.3.3)
pry (>= 0.9.10)
puma (2.9.1)
puma (2.11.1)
rack (>= 1.1, < 2.0)
qunit-rails (0.0.7)
railties
@ -273,18 +325,18 @@ GEM
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
raindrops (0.13.0)
rake (10.3.2)
rake-compiler (0.9.3)
rake (10.4.2)
rake-compiler (0.9.4)
rake
rb-fsevent (0.9.4)
rb-inotify (0.9.5)
ffi (>= 0.5.0)
rbtrace (0.4.5)
rbtrace (0.4.7)
ffi (>= 1.0.6)
msgpack (>= 0.4.3)
trollop (>= 1.16.2)
redcarpet (3.1.2)
redis (3.1.0)
redcarpet (3.2.2)
redis (3.2.1)
redis-namespace (1.5.1)
redis (~> 3.0, >= 3.0.4)
ref (1.0.5)
@ -339,9 +391,9 @@ GEM
shoulda-context (1.2.1)
shoulda-matchers (2.7.0)
activesupport (>= 3.0.0)
sidekiq (3.2.5)
celluloid (= 0.15.2)
connection_pool (>= 2.0.0)
sidekiq (3.3.1)
celluloid (>= 0.16.0)
connection_pool (>= 2.1.1)
json
redis (>= 3.0.6)
redis-namespace (>= 1.3.1)
@ -375,22 +427,23 @@ GEM
therubyracer (0.12.1)
libv8 (~> 3.16.14.0)
ref
thin (1.6.2)
daemons (>= 1.0.9)
eventmachine (>= 1.0.0)
rack (>= 1.0.0)
thin (1.6.3)
daemons (~> 1.0, >= 1.0.9)
eventmachine (~> 1.0)
rack (~> 1.0)
thor (0.19.1)
thread_safe (0.3.4)
tilt (1.4.1)
timecop (0.7.1)
timers (1.1.0)
timers (4.0.1)
hitimes
treetop (1.4.15)
polyglot
polyglot (>= 0.3.1)
trollop (2.0)
trollop (2.1.1)
tzinfo (1.2.2)
thread_safe (~> 0.1)
uglifier (2.5.3)
uglifier (2.6.0)
execjs (>= 0.3.0)
json (>= 1.8.0)
unf (0.1.4)
@ -405,6 +458,7 @@ PLATFORMS
ruby
DEPENDENCIES
6to5
actionpack-action_caching
active_model_serializers (~> 0.8.0)
annotate
@ -412,21 +466,21 @@ DEPENDENCIES
better_errors
binding_of_caller
certified
email_reply_parser-discourse
email_reply_parser
ember-rails
ember-source (= 1.6.0.beta.2)
ember-source (= 1.9.0.beta.4)
eventmachine
fabrication (= 2.9.8)
fakeweb (~> 1.3.0)
fast_blank
fast_xor
fast_xs
fastimage
fastimage_discourse
flamegraph
fog (= 1.22.1)
fog (= 1.26.0)
foreman
gctools
handlebars-source (= 1.3.0)
handlebars-source (= 2.0.0)
highline
hiredis
htmlentities
@ -454,7 +508,7 @@ DEPENDENCIES
omniauth-twitter
onebox
openid-redis-store
pg (= 0.15.1)
pg
pry-nav
pry-rails
puma

View File

@ -7,66 +7,26 @@ GIT
activemodel (>= 3.0)
GIT
remote: https://github.com/rails/arel.git
revision: 590c784a30b13153667f8db7915998d7731e24e5
remote: https://github.com/SamSaffron/rails-observers.git
revision: 7d2222d758603a004f6599f82a7068ffeb2d7ebf
specs:
arel (6.0.0.beta2)
rails-observers (0.1.2)
activemodel (> 4.0)
GIT
remote: https://github.com/rails/rails.git
revision: eb26f24bde62cbbcd8ef0e7ee9c64060b098baff
remote: https://github.com/SamSaffron/seed-fu.git
revision: d93df3b6364ea938d87c5629bf950b0d1ffe037e
branch: discourse
specs:
actionmailer (4.2.0.beta4)
actionpack (= 4.2.0.beta4)
actionview (= 4.2.0.beta4)
activejob (= 4.2.0.beta4)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 1.0, >= 1.0.4)
actionpack (4.2.0.beta4)
actionview (= 4.2.0.beta4)
activesupport (= 4.2.0.beta4)
rack (~> 1.6.0.beta)
rack-test (~> 0.6.2)
rails-dom-testing (~> 1.0, >= 1.0.4)
rails-html-sanitizer (~> 1.0, >= 1.0.1)
actionview (4.2.0.beta4)
activesupport (= 4.2.0.beta4)
builder (~> 3.1)
erubis (~> 2.7.0)
rails-dom-testing (~> 1.0, >= 1.0.4)
rails-html-sanitizer (~> 1.0, >= 1.0.1)
activejob (4.2.0.beta4)
activesupport (= 4.2.0.beta4)
globalid (>= 0.3.0)
activemodel (4.2.0.beta4)
activesupport (= 4.2.0.beta4)
builder (~> 3.1)
activerecord (4.2.0.beta4)
activemodel (= 4.2.0.beta4)
activesupport (= 4.2.0.beta4)
arel (>= 6.0.0.beta2, < 6.1)
activesupport (4.2.0.beta4)
i18n (>= 0.7.0.beta1, < 0.8)
json (~> 1.7, >= 1.7.7)
minitest (~> 5.1)
thread_safe (~> 0.1)
tzinfo (~> 1.1)
rails (4.2.0.beta4)
actionmailer (= 4.2.0.beta4)
actionpack (= 4.2.0.beta4)
actionview (= 4.2.0.beta4)
activejob (= 4.2.0.beta4)
activemodel (= 4.2.0.beta4)
activerecord (= 4.2.0.beta4)
activesupport (= 4.2.0.beta4)
bundler (>= 1.3.0, < 2.0)
railties (= 4.2.0.beta4)
sprockets-rails (~> 3.0.0.beta1)
railties (4.2.0.beta4)
actionpack (= 4.2.0.beta4)
activesupport (= 4.2.0.beta4)
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
seed-fu (2.3.3)
activerecord (>= 3.1)
activesupport (>= 3.1)
GIT
remote: https://github.com/rails/arel.git
revision: 98fc25991137ee09b6800578117f8c1c322680f2
specs:
arel (6.0.0)
GIT
remote: https://github.com/rails/sass-rails.git
@ -78,6 +38,61 @@ GIT
sprockets (~> 2.12)
sprockets-rails (>= 2.0, < 4.0)
PATH
remote: ../rails
specs:
actionmailer (5.0.0.alpha)
actionpack (= 5.0.0.alpha)
actionview (= 5.0.0.alpha)
activejob (= 5.0.0.alpha)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 1.0, >= 1.0.5)
actionpack (5.0.0.alpha)
actionview (= 5.0.0.alpha)
activesupport (= 5.0.0.alpha)
rack (~> 1.6.0.beta2)
rack-test (~> 0.6.2)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.1)
actionview (5.0.0.alpha)
activesupport (= 5.0.0.alpha)
builder (~> 3.1)
erubis (~> 2.7.0)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.1)
activejob (5.0.0.alpha)
activesupport (= 5.0.0.alpha)
globalid (>= 0.3.0)
activemodel (5.0.0.alpha)
activesupport (= 5.0.0.alpha)
builder (~> 3.1)
activerecord (5.0.0.alpha)
activemodel (= 5.0.0.alpha)
activesupport (= 5.0.0.alpha)
arel (~> 6.0)
activesupport (5.0.0.alpha)
i18n (>= 0.7.0.beta1, < 0.8)
json (~> 1.7, >= 1.7.7)
minitest (~> 5.1)
thread_safe (~> 0.1)
tzinfo (~> 1.1)
rails (5.0.0.alpha)
actionmailer (= 5.0.0.alpha)
actionpack (= 5.0.0.alpha)
actionview (= 5.0.0.alpha)
activejob (= 5.0.0.alpha)
activemodel (= 5.0.0.alpha)
activerecord (= 5.0.0.alpha)
activesupport (= 5.0.0.alpha)
bundler (>= 1.3.0, < 2.0)
railties (= 5.0.0.alpha)
sprockets-rails
railties (5.0.0.alpha)
actionpack (= 5.0.0.alpha)
activesupport (= 5.0.0.alpha)
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
PATH
remote: vendor/gems/rails_multisite
specs:
@ -150,7 +165,7 @@ GEM
fastimage (1.6.3)
addressable (~> 2.3, >= 2.3.5)
ffi (1.9.5)
flamegraph (0.0.8)
flamegraph (0.0.9)
fast_stack
fog (1.22.1)
fog-brightbox
@ -285,7 +300,7 @@ GEM
openid-redis-store (0.0.2)
redis
ruby-openid
pg (0.15.1)
pg (0.18.0.pre20141117110243)
polyglot (0.3.5)
progress (3.0.1)
pry (0.10.1)
@ -300,7 +315,7 @@ GEM
rack (>= 1.1, < 2.0)
qunit-rails (0.0.7)
railties
rack (1.6.0.beta)
rack (1.6.0.beta2)
rack-mini-profiler (0.9.2)
rack (>= 1.1.3)
rack-openid (1.3.1)
@ -312,16 +327,14 @@ GEM
rack (>= 1.0)
rails-deprecated_sanitizer (1.0.3)
activesupport (>= 4.2.0.alpha)
rails-dom-testing (1.0.4)
rails-dom-testing (1.0.5)
activesupport (>= 4.2.0.beta, < 5.0)
nokogiri (~> 1.6.0)
rails-deprecated_sanitizer (>= 1.0.1)
rails-html-sanitizer (1.0.1)
loofah (~> 2.0)
rails-observers (0.1.2)
activemodel (~> 4.0)
raindrops (0.13.0)
rake (10.3.2)
rake (10.4.0)
rake-compiler (0.9.3)
rake
rb-fsevent (0.9.4)
@ -373,9 +386,6 @@ GEM
nokogiri (>= 1.4.4)
nokogumbo (= 1.1.12)
sass (3.2.19)
seed-fu (2.3.3)
activerecord (>= 3.1, < 4.2)
activesupport (>= 3.1, < 4.2)
shoulda (3.5.0)
shoulda-context (~> 1.0, >= 1.0.1)
shoulda-matchers (>= 1.4.1, < 3.0)
@ -498,7 +508,7 @@ DEPENDENCIES
omniauth-twitter
onebox
openid-redis-store
pg (= 0.15.1)
pg (= 0.18.0.pre20141117110243)
pry-nav
pry-rails
puma
@ -506,7 +516,7 @@ DEPENDENCIES
rack-mini-profiler
rack-protection
rails!
rails-observers
rails-observers!
rails_multisite!
rake
rb-fsevent
@ -525,7 +535,7 @@ DEPENDENCIES
sanitize
sass
sass-rails!
seed-fu (~> 2.3.3)
seed-fu!
shoulda
sidekiq
simple-rss

View File

@ -10,13 +10,14 @@ To learn more about the philosophy and goals of the project, [visit **discourse.
## Screenshots
[![](https://raw2.github.com/discourse/discourse-docimages/master/readme/boing-boing-latest-small2.png)](http://bbs.boingboing.net)
[![](https://raw2.github.com/discourse/discourse-docimages/master/readme/how-to-geek-profile-small2.png?breakcache=1)](http://discuss.howtogeek.com)
[![](https://raw2.github.com/discourse/discourse-docimages/master/readme/new-relic-categories-small2.png)](http://discuss.newrelic.com)
[![](https://raw2.github.com/discourse/discourse-docimages/master/readme/turtle-rock-topic-small2.jpg)](https://talk.turtlerockstudios.com/)
[![](https://raw.github.com/discourse/discourse-docimages/master/readme/nexus-7-mobile-discourse-small3.png)](http://discuss.atom.io)
[![](https://raw.github.com/discourse/discourse-docimages/master/readme/iphone-5s-mobile-discourse-small4.png)](http://discourse.soylent.me)
[![](https://raw.githubusercontent.com/discourse/discourse-docimages/master/readme/boing-boing-latest-small2.png)](http://bbs.boingboing.net)
[![](https://raw.githubusercontent.com/discourse/discourse-docimages/master/readme/how-to-geek-profile-small2.png)](http://discuss.howtogeek.com)
[![](https://raw.githubusercontent.com/discourse/discourse-docimages/master/readme/new-relic-categories-small2.png)](http://discuss.newrelic.com)
[![](https://raw.githubusercontent.com/discourse/discourse-docimages/master/readme/turtle-rock-topic-small2.jpg)](https://talk.turtlerockstudios.com/)
[![](https://raw.githubusercontent.com/discourse/discourse-docimages/master/readme/nexus-7-mobile-discourse-small3.png)](http://discuss.atom.io)
[![](https://raw.githubusercontent.com/discourse/discourse-docimages/master/readme/iphone-5s-mobile-discourse-small4.png)](http://discourse.soylent.me)
Browse [lots more notable Discourse instances](http://www.discourse.org/faq/customers/).
## Development
@ -56,8 +57,8 @@ Plus *lots* of Ruby Gems, a complete list of which is at [/master/Gemfile](https
## Contributing
[![Build Status](https://travis-ci.org/discourse/discourse.png)](https://travis-ci.org/discourse/discourse)
[![Code Climate](https://codeclimate.com/github/discourse/discourse.png)](https://codeclimate.com/github/discourse/discourse)
[![Build Status](https://travis-ci.org/discourse/discourse.svg)](https://travis-ci.org/discourse/discourse)
[![Code Climate](https://codeclimate.com/github/discourse/discourse.svg)](https://codeclimate.com/github/discourse/discourse)
Discourse is **100% free** and **open-source**. We encourage and support an active, healthy community that
accepts contributions from the public &ndash; including you!

4
Vagrantfile vendored
View File

@ -3,8 +3,8 @@
# See https://github.com/discourse/discourse/blob/master/docs/VAGRANT.md
#
Vagrant.configure("2") do |config|
config.vm.box = 'discourse-0.9.9.13'
config.vm.box_url = "https://d3fvb7b7auiut8.cloudfront.net/discourse-0.9.9.13.box"
config.vm.box= "discourse/discourse-0.9.9.15.box"
config.vm.box_url = "https://vagrantcloud.com/discourse/discourse-0.9.9.15.box"
# Make this VM reachable on the host network as well, so that other
# VM's running other browsers can access our dev server.

View File

@ -1,12 +1,5 @@
<%
if Rails.env.development?
require_asset ("development/list-view.js")
else
require_asset ("production/list-view.js")
end
require_asset("main_include_admin.js")
DiscoursePluginRegistry.admin_javascripts.each { |js| require_asset(js) }
%>

View File

@ -31,4 +31,4 @@ export default Ember.Component.extend({
_refreshOnReset: function() {
this.$("input").select2("data", this.get("selected").map(this._format));
}.observes("selected")
});
});

View File

@ -0,0 +1,18 @@
export default Ember.Component.extend({
tagName: 'li',
classNameBindings: ['active'],
router: function() {
return this.container.lookup('router:main');
}.property(),
active: function() {
const route = this.get('route');
if (!route) { return; }
const routeParam = this.get('routeParam'),
router = this.get('router');
return routeParam ? router.isActive(route, routeParam) : router.isActive(route);
}.property('router.url', 'route')
});

View File

@ -0,0 +1,24 @@
/**
An input field for a color.
@param hexValue is a reference to the color's hex value.
@param brightnessValue is a number from 0 to 255 representing the brightness of the color. See ColorSchemeColor.
@params valid is a boolean indicating if the input field is a valid color.
**/
export default Ember.Component.extend({
hexValueChanged: function() {
var hex = this.get('hexValue');
if (this.get('valid')) {
this.$('input').attr('style', 'color: ' + (this.get('brightnessValue') > 125 ? 'black' : 'white') + '; background-color: #' + hex + ';');
} else {
this.$('input').attr('style', '');
}
}.observes('hexValue', 'brightnessValue', 'valid'),
_triggerHexChanged: function() {
var self = this;
Em.run.schedule('afterRender', function() {
self.hexValueChanged();
});
}.on('didInsertElement')
});

View File

@ -1,32 +0,0 @@
/**
An input field for a color.
@param hexValue is a reference to the color's hex value.
@param brightnessValue is a number from 0 to 255 representing the brightness of the color. See ColorSchemeColor.
@params valid is a boolean indicating if the input field is a valid color.
@class Discourse.ColorInputComponent
@extends Ember.Component
@namespace Discourse
@module Discourse
**/
Discourse.ColorInputComponent = Ember.Component.extend({
layoutName: 'components/color-input',
hexValueChanged: function() {
var hex = this.get('hexValue');
if (this.get('valid')) {
this.$('input').attr('style', 'color: ' + (this.get('brightnessValue') > 125 ? 'black' : 'white') + '; background-color: #' + hex + ';');
} else {
this.$('input').attr('style', '');
}
}.observes('hexValue', 'brightnessValue', 'valid'),
didInsertElement: function() {
var self = this;
this._super();
Em.run.schedule('afterRender', function() {
self.hexValueChanged();
});
}
});

View File

@ -9,6 +9,13 @@ export default Ember.Component.extend({
].filter(Boolean).join(", ");
}.property("location.{city,region,country}"),
otherAccountsToDelete: function() {
// can only delete up to 50 accounts at a time
var total = Math.min(50, this.get("totalOthersWithSameIP") || 0);
var visible = Math.min(50, this.get("other_accounts.length") || 0);
return Math.max(visible, total);
}.property("other_accounts", "totalOthersWithSameIP"),
actions: {
lookup: function () {
var self = this;
@ -24,10 +31,18 @@ export default Ember.Component.extend({
if (!this.get("other_accounts")) {
this.set("otherAccountsLoading", true);
Discourse.AdminUser.findAll("active", {
var data = {
"ip": this.get("ip"),
"exclude": this.get("user_id")
}).then(function (users) {
"exclude": this.get("userId"),
"order": "trust_level DESC"
};
Discourse.ajax("/admin/users/total-others-with-same-ip.json", { data: data }).then(function (result) {
self.set("totalOthersWithSameIP", result.total);
});
Discourse.AdminUser.findAll("active", data).then(function (users) {
self.setProperties({
other_accounts: users,
otherAccountsLoading: false,
@ -38,6 +53,30 @@ export default Ember.Component.extend({
hide: function () {
this.set("show", false);
},
deleteOtherAccounts: function() {
var self = this;
bootbox.confirm(I18n.t("ip_lookup.confirm_delete_other_accounts"), I18n.t("no_value"), I18n.t("yes_value"), function (confirmed) {
if (confirmed) {
self.setProperties({
other_accounts: null,
otherAccountsLoading: true,
totalOthersWithSameIP: null
});
Discourse.ajax("/admin/users/delete-others-with-same-ip.json", {
type: "DELETE",
data: {
"ip": self.get("ip"),
"exclude": self.get("userId"),
"order": "trust_level DESC"
}
}).then(function() {
self.send("lookup");
});
}
});
}
}
});

View File

@ -0,0 +1,53 @@
/**
Provide a nice GUI for a pipe-delimited list in the site settings.
@param settingValue is a reference to SiteSetting.value.
@param choices is a reference to SiteSetting.choices
**/
export default Ember.Component.extend({
_select2FormatSelection: function(selectedObject, jqueryWrapper, htmlEscaper) {
var text = selectedObject.text;
if (text.length <= 6) {
jqueryWrapper.closest('li.select2-search-choice').css({"border-bottom": '7px solid #'+text});
}
return htmlEscaper(text);
},
_initializeSelect2: function(){
var options = {
multiple: false,
separator: "|",
tokenSeparators: ["|"],
tags : this.get("choices") || [],
width: 'off',
dropdownCss: this.get("choices") ? {} : {display: 'none'}
};
var settingName = this.get('settingName');
if (typeof settingName === 'string' && settingName.indexOf('colors') > -1) {
options.formatSelection = this._select2FormatSelection;
}
var self = this;
this.$("input").select2(options).on("change", function(obj) {
self.set("settingValue", obj.val.join("|"));
self.refreshSortables();
});
this.refreshSortables();
}.on('didInsertElement'),
refreshOnReset: function() {
this.$("input").select2("val", this.get("settingValue").split("|"));
}.observes("settingValue"),
refreshSortables: function() {
var self = this;
this.$("ul.select2-choices").sortable().on('sortupdate', function() {
self.$("input").select2("onSortEnd");
});
}
});

View File

@ -1,59 +0,0 @@
/**
Provide a nice GUI for a pipe-delimited list in the site settings.
@param settingValue is a reference to SiteSetting.value.
@param choices is a reference to SiteSetting.choices
@class Discourse.ListSettingComponent
@extends Ember.Component
@namespace Discourse
@module Discourse
**/
Discourse.ListSettingComponent = Ember.Component.extend({
tagName: 'div',
_select2FormatSelection: function(selectedObject, jqueryWrapper, htmlEscaper) {
var text = selectedObject.text;
if (text.length <= 6) {
jqueryWrapper.closest('li.select2-search-choice').css({"border-bottom": '7px solid #'+text});
}
return htmlEscaper(text);
},
didInsertElement: function(){
var select2_options = {
multiple: false,
separator: "|",
tokenSeparators: ["|"],
tags : this.get("choices") || [],
width: 'off',
dropdownCss: this.get("choices") ? {} : {display: 'none'}
};
var settingName = this.get('settingName');
if (typeof settingName === 'string' && settingName.indexOf('colors') > -1) {
select2_options.formatSelection = this._select2FormatSelection;
}
this.$("input").select2(select2_options).on("change", function(obj) {
this.set("settingValue", obj.val.join("|"));
this.refreshSortables();
}.bind(this));
this.refreshSortables();
},
refreshOnReset: function() {
this.$("input").select2("val", this.get("settingValue").split("|"));
}.observes("settingValue"),
refreshSortables: function() {
this.$("ul.select2-choices").sortable().on('sortupdate', function() {
this.$("input").select2("onSortEnd");
}.bind(this));
}
});

View File

@ -9,13 +9,8 @@
error="errorAction"
uploadText="UPLOAD"
}}
@class ResumableUploadComponent
@extends Ember.Component
@namespace Discourse
@module Discourse
**/
Discourse.ResumableUploadComponent = Ember.Component.extend({
Discourse.ResumableUploadComponent = Ember.Component.extend(Discourse.StringBuffer, {
tagName: "button",
classNames: ["btn", "ru"],
classNameBindings: ["isUploading"],
@ -25,7 +20,7 @@ Discourse.ResumableUploadComponent = Ember.Component.extend({
isUploading: false,
progress: 0,
shouldRerender: Discourse.View.renderIfChanged("isUploading", "progress"),
rerenderTriggers: ['isUploading', 'progress'],
text: function() {
if (this.get("isUploading")) {
@ -35,7 +30,7 @@ Discourse.ResumableUploadComponent = Ember.Component.extend({
}
}.property("isUploading", "progress"),
render: function(buffer) {
renderString: function(buffer) {
var icon = this.get("isUploading") ? "times" : "upload";
buffer.push("<i class='fa fa-" + icon + "'></i>");
buffer.push("<span class='ru-label'>" + this.get("text") + "</span>");

View File

@ -53,6 +53,9 @@ export default Ember.Controller.extend({
actions: {
refreshProblems: function() {
this.loadProblems();
},
showTrafficReport: function() {
this.set("showTrafficReport", true);
}
}

View File

@ -36,8 +36,12 @@ export default DiscourseController.extend({
data: { email_address: this.get('testEmailAddress') }
}).then(function () {
self.set('sentTestEmail', true);
}).catch(function () {
bootbox.alert(I18n.t('admin.email.test_error'));
}, function(e) {
if (e.responseJSON && e.responseJSON.errors) {
bootbox.alert(I18n.t('admin.email.error', { server_error: e.responseJSON.errors[0] }));
} else {
bootbox.alert(I18n.t('admin.email.test_error'));
}
}).finally(function() {
self.set('sendingEmail', false);
});

View File

@ -0,0 +1,20 @@
export default Ember.ArrayController.extend({
sortProperties: ["name"],
actions: {
emojiUploaded: function (emoji) {
this.pushObject(emoji);
},
destroy: function(emoji) {
var self = this;
return bootbox.confirm(I18n.t("admin.emoji.delete_confirm", { name: emoji.name }), I18n.t("no_value"), I18n.t("yes_value"), function (destroy) {
if (destroy) {
return Discourse.ajax("/admin/customize/emojis/" + emoji.name, { type: "DELETE" }).then(function() {
self.removeObject(emoji);
});
}
});
}
}
});

View File

@ -5,7 +5,7 @@ export default Ember.ArrayController.extend({
adminActiveFlagsView: Em.computed.equal("query", "active"),
actions: {
disagreeFlags: function (flaggedPost) {
disagreeFlags(flaggedPost) {
var self = this;
flaggedPost.disagreeFlags().then(function () {
self.removeObject(flaggedPost);
@ -14,7 +14,7 @@ export default Ember.ArrayController.extend({
});
},
deferFlags: function (flaggedPost) {
deferFlags(flaggedPost) {
var self = this;
flaggedPost.deferFlags().then(function () {
self.removeObject(flaggedPost);
@ -23,12 +23,12 @@ export default Ember.ArrayController.extend({
});
},
doneTopicFlags: function(item) {
doneTopicFlags(item) {
this.send("disagreeFlags", item);
},
},
loadMore: function(){
loadMore(){
var flags = this.get("model");
return Discourse.FlaggedPost.findAll(this.get("query"),flags.length+1).then(function(data){
if(data.length===0){

View File

@ -1,51 +1,86 @@
export default Em.ObjectController.extend({
needs: ['adminGroups'],
members: null,
needs: ['adminGroupsType'],
disableSave: false,
currentPage: function() {
if (this.get("user_count") === 0) { return 0; }
return Math.floor(this.get("offset") / this.get("limit")) + 1;
}.property("limit", "offset", "user_count"),
totalPages: function() {
if (this.get("user_count") === 0) { return 0; }
return Math.floor(this.get("user_count") / this.get("limit")) + 1;
}.property("limit", "user_count"),
showingFirst: Em.computed.lte("currentPage", 1),
showingLast: Discourse.computed.propertyEqual("currentPage", "totalPages"),
aliasLevelOptions: function() {
return [
{ name: I18n.t("groups.alias_levels.nobody"), value: 0},
{ name: I18n.t("groups.alias_levels.mods_and_admins"), value: 2},
{ name: I18n.t("groups.alias_levels.members_mods_and_admins"), value: 3},
{ name: I18n.t("groups.alias_levels.everyone"), value: 99}
{ name: I18n.t("groups.alias_levels.nobody"), value: 0 },
{ name: I18n.t("groups.alias_levels.mods_and_admins"), value: 2 },
{ name: I18n.t("groups.alias_levels.members_mods_and_admins"), value: 3 },
{ name: I18n.t("groups.alias_levels.everyone"), value: 99 }
];
}.property(),
usernames: function(key, value) {
var members = this.get('members');
if (arguments.length > 1) {
this.set('_usernames', value);
} else {
var usernames;
if(members) {
usernames = members.map(function(user) {
return user.get('username');
}).join(',');
}
this.set('_usernames', usernames);
}
return this.get('_usernames');
}.property('members.@each.username'),
actions: {
next: function() {
if (this.get("showingLast")) { return; }
var group = this.get("model"),
offset = Math.min(group.get("offset") + group.get("limit"), group.get("user_count"));
group.set("offset", offset);
return group.findMembers();
},
previous: function() {
if (this.get("showingFirst")) { return; }
var group = this.get("model"),
offset = Math.max(group.get("offset") - group.get("limit"), 0);
group.set("offset", offset);
return group.findMembers();
},
removeMember: function(member) {
var self = this,
message = I18n.t("admin.groups.delete_member_confirm", { username: member.get("username"), group: this.get("name") });
return bootbox.confirm(message, I18n.t("no_value"), I18n.t("yes_value"), function(confirm) {
if (confirm) {
self.get("model").removeMember(member);
}
});
},
addMembers: function() {
if (Em.isEmpty(this.get("usernames"))) { return; }
this.get("model").addMembers(this.get("usernames"));
// clear the user selector
this.set("usernames", null);
},
save: function() {
var self = this,
group = this.get('model');
group = this.get('model'),
groupsController = this.get("controllers.adminGroupsType");
self.set('disableSave', true);
this.set('disableSave', true);
var promise;
if (group.get('id')) {
promise = group.saveWithUsernames(this.get('usernames'));
if (group.get("id")) {
promise = group.save();
} else {
promise = group.createWithUsernames(this.get('usernames')).then(function() {
var groupsController = self.get('controllers.adminGroups');
promise = group.create().then(function() {
groupsController.addObject(group);
});
}
promise.then(function() {
self.send('showGroup', group);
self.transitionToRoute("adminGroup", group);
}, function(e) {
var message = $.parseJSON(e.responseText).errors;
bootbox.alert(message);
@ -56,12 +91,13 @@ export default Em.ObjectController.extend({
destroy: function() {
var group = this.get('model'),
groupsController = this.get('controllers.adminGroups'),
groupsController = this.get('controllers.adminGroupsType'),
self = this;
bootbox.confirm(I18n.t("admin.groups.delete_confirm"), I18n.t("no_value"), I18n.t("yes_value"), function(result) {
if (result) {
self.set('disableSave', true);
this.set('disableSave', true);
bootbox.confirm(I18n.t("admin.groups.delete_confirm"), I18n.t("no_value"), I18n.t("yes_value"), function(confirmed) {
if (confirmed) {
group.destroy().then(function() {
groupsController.get('model').removeObject(group);
self.transitionToRoute('adminGroups.index');

View File

@ -0,0 +1,16 @@
export default Ember.ArrayController.extend({
sortProperties: ['name'],
refreshingAutoGroups: false,
actions: {
refreshAutoGroups: function(){
var self = this;
this.set('refreshingAutoGroups', true);
Discourse.ajax('/admin/groups/refresh_automatic_groups', {type: 'POST'}).then(function() {
self.transitionToRoute("adminGroupsType", "automatic").then(function() {
self.set('refreshingAutoGroups', false);
});
});
}
}
});

View File

@ -1,24 +0,0 @@
export default Ember.ArrayController.extend({
sortProperties: ['name'],
refreshingAutoGroups: false,
actions: {
refreshAutoGroups: function(){
var self = this,
groups = this.get('model');
self.set('refreshingAutoGroups', true);
this.transitionToRoute('adminGroups.index').then(function() {
Discourse.ajax('/admin/groups/refresh_automatic_groups', {type: 'POST'}).then(function() {
return Discourse.Group.findAll().then(function(newGroups) {
groups.clear();
groups.addObjects(newGroups);
}).finally(function() {
self.set('refreshingAutoGroups', false);
});
});
});
}
}
});

View File

@ -2,6 +2,10 @@ export default Ember.ObjectController.extend({
editing: false,
savedIpAddress: null,
isRange: function() {
return this.get("ip_address").indexOf("/") > 0;
}.property("ip_address"),
actions: {
allow: function(record) {
record.set('action_name', 'do_nothing');

View File

@ -1,18 +1,24 @@
import { outputExportResult } from 'discourse/lib/export-result';
export default Ember.ArrayController.extend(Discourse.Presence, {
loading: false,
actions: {
clearBlock: function(row){
clearBlock(row){
row.clearBlock().then(function(){
// feeling lazy
window.location.reload();
});
},
exportScreenedEmailList() {
Discourse.ExportCsv.exportScreenedEmailList().then(outputExportResult);
}
},
show: function() {
show() {
var self = this;
this.set('loading', true);
self.set('loading', true);
Discourse.ScreenedEmail.findAll().then(function(result) {
self.set('model', result);
self.set('loading', false);

View File

@ -1,19 +1,46 @@
import { outputExportResult } from 'discourse/lib/export-result';
export default Ember.ArrayController.extend(Discourse.Presence, {
loading: false,
itemController: 'admin-log-screened-ip-address',
filter: null,
show: function() {
show: Discourse.debounce(function() {
var self = this;
this.set('loading', true);
Discourse.ScreenedIpAddress.findAll().then(function(result) {
self.set('loading', true);
Discourse.ScreenedIpAddress.findAll(this.get("filter")).then(function(result) {
self.set('model', result);
self.set('loading', false);
});
},
}, 250).observes("filter"),
actions: {
recordAdded: function(arg) {
recordAdded(arg) {
this.get("model").unshiftObject(arg);
},
rollUp() {
const self = this;
return bootbox.confirm(I18n.t("admin.logs.screened_ips.roll_up_confirm"), I18n.t("no_value"), I18n.t("yes_value"), function (confirmed) {
if (confirmed) {
self.set("loading", true);
return Discourse.ScreenedIpAddress.rollUp().then(function(results) {
if (results && results.subnets) {
if (results.subnets.length > 0) {
self.send("show");
bootbox.alert(I18n.t("admin.logs.screened_ips.rolled_up_some_subnets", { subnets: results.subnets.join(", ") }));
} else {
self.set("loading", false);
bootbox.alert(I18n.t("admin.logs.screened_ips.rolled_up_no_subnet"));
}
}
});
}
});
},
exportScreenedIpList() {
Discourse.ExportCsv.exportScreenedIpList().then(outputExportResult);
}
}
});

View File

@ -1,12 +1,20 @@
import { outputExportResult } from 'discourse/lib/export-result';
export default Ember.ArrayController.extend(Discourse.Presence, {
loading: false,
show: function() {
var self = this;
this.set('loading', true);
show() {
const self = this;
self.set('loading', true);
Discourse.ScreenedUrl.findAll().then(function(result) {
self.set('model', result);
self.set('loading', false);
});
},
actions: {
exportScreenedUrlList() {
Discourse.ExportCsv.exportScreenedUrlList().then(outputExportResult);
}
}
});

View File

@ -1,65 +1,98 @@
/**
This controller supports the interface for listing staff action logs in the admin section.
import { outputExportResult } from 'discourse/lib/export-result';
@class AdminLogsStaffActionLogsController
@extends Ember.ArrayController
@namespace Discourse
@module Discourse
**/
export default Ember.ArrayController.extend(Discourse.Presence, {
loading: false,
filters: {},
filters: null,
show: function() {
var self = this;
this.set('loading', true);
Discourse.URL.set('queryParams', this.get('filters')); // TODO: doesn't work
Discourse.StaffActionLog.findAll(this.get('filters')).then(function(result) {
self.set('model', result);
self.set('loading', false);
});
}.observes('filters.action_name', 'filters.acting_user', 'filters.target_user', 'filters.subject'),
filtersExists: function() {
return (_.size(this.get('filters')) > 0);
}.property('filters.action_name', 'filters.acting_user', 'filters.target_user', 'filters.subject'),
filtersExists: Ember.computed.gt('filterCount', 0),
actionFilter: function() {
if (this.get('filters.action_name')) {
return I18n.t("admin.logs.staff_actions.actions." + this.get('filters.action_name'));
var name = this.get('filters.action_name');
if (name) {
return I18n.t("admin.logs.staff_actions.actions." + name);
} else {
return null;
}
}.property('filters.action_name'),
showInstructions: function() {
return this.get('model.length') > 0;
}.property('loading', 'model.length'),
showInstructions: Ember.computed.gt('model.length', 0),
refresh: function() {
var self = this;
this.set('loading', true);
var filters = this.get('filters'),
params = {},
count = 0;
// Don't send null values
Object.keys(filters).forEach(function(k) {
var val = filters.get(k);
if (val) {
params[k] = val;
count += 1;
}
});
this.set('filterCount', count);
Discourse.StaffActionLog.findAll(params).then(function(result) {
self.set('model', result);
}).finally(function() {
self.set('loading', false);
});
},
resetFilters: function() {
this.set('filters', Ember.Object.create());
this.refresh();
}.on('init'),
_changeFilters: function(props) {
this.get('filters').setProperties(props);
this.refresh();
},
actions: {
clearFilter: function(key) {
delete this.get('filters')[key];
this.notifyPropertyChange('filters');
var changed = {};
// Special case, clear all action related stuff
if (key === 'actionFilter') {
changed.action_name = null;
changed.action_id = null;
changed.custom_type = null;
} else {
changed[key] = null;
}
this._changeFilters(changed);
},
clearAllFilters: function() {
this.set('filters', {});
this.resetFilters();
},
filterByAction: function(action) {
this.set('filters.action_name', action);
filterByAction: function(logItem) {
this._changeFilters({
action_name: logItem.get('action_name'),
action_id: logItem.get('action'),
custom_type: logItem.get('custom_type')
});
},
filterByStaffUser: function(acting_user) {
this.set('filters.acting_user', acting_user.username);
this._changeFilters({ acting_user: acting_user.username });
},
filterByTargetUser: function(target_user) {
this.set('filters.target_user', target_user.username);
this._changeFilters({ target_user: target_user.username });
},
filterBySubject: function(subject) {
this.set('filters.subject', subject);
this._changeFilters({ subject: subject });
},
exportStaffActionLogs: function() {
Discourse.ExportCsv.exportStaffActionLogs().then(outputExportResult);
}
}
});

View File

@ -0,0 +1,6 @@
export default Ember.ArrayController.extend({
adminRoutes: function() {
return this.get('model').map(p => p.admin_route).compact();
}.property()
});

View File

@ -3,29 +3,16 @@ export default Ember.ObjectController.extend({
needs: ['adminSiteSettings'],
filteredContent: function() {
if (!this.get('categoryNameKey')) { return Em.A(); }
var category = this.get('controllers.adminSiteSettings.content').find(function(siteSettingCategory) {
return siteSettingCategory.nameKey === this.get('categoryNameKey');
}, this);
if (!this.get('categoryNameKey')) { return []; }
var category = this.get('controllers.adminSiteSettings.content').findProperty('nameKey', this.get('categoryNameKey'));
if (category) {
return category.siteSettings;
} else {
return Em.A();
return [];
}
}.property('controllers.adminSiteSettings.content', 'categoryNameKey'),
emptyContentHandler: function() {
if (this.get('filteredContent').length < 1) {
if ( this.get('controllers.adminSiteSettings.filtered') ) {
this.transitionToRoute('adminSiteSettingsCategory', 'all_results');
} else {
this.transitionToRoute('adminSiteSettings');
}
}
}.observes('filteredContent'),
actions: {
/**

View File

@ -1,11 +1,3 @@
/**
This controller supports the interface for SiteSettings.
@class AdminSiteSettingsController
@extends Ember.ArrayController
@namespace Discourse
@module Discourse
**/
export default Ember.ArrayController.extend(Discourse.Presence, {
filter: null,
onlyOverridden: false,
@ -28,6 +20,7 @@ export default Ember.ArrayController.extend(Discourse.Presence, {
if ((filter === undefined || filter.length < 1) && !this.get('onlyOverridden')) {
this.set('model', this.get('allSiteSettings'));
this.transitionToRoute("adminSiteSettings");
return;
}
@ -50,20 +43,19 @@ export default Ember.ArrayController.extend(Discourse.Presence, {
});
if (matches.length > 0) {
matchesGroupedByCategory[0].siteSettings.pushObjects(matches);
matchesGroupedByCategory.pushObject({
nameKey: settingsCategory.nameKey,
name: settingsCategory.name,
siteSettings: matches});
}
});
this.set('model', matchesGroupedByCategory);
this.transitionToRoute("adminSiteSettingsCategory", "all_results");
}, 250).observes('filter', 'onlyOverridden'),
actions: {
clearFilter: function() {
this.set('filter', '');
this.set('onlyOverridden', false);
this.setProperties({
filter: '',
onlyOverridden: false
});
}
}

View File

@ -4,7 +4,7 @@ export default Ember.ObjectController.extend({
saveDisabled: function() {
if (this.get('saving')) { return true; }
if ((!this.get('allow_blank')) && Ember.empty(this.get('value'))) { return true; }
if ((!this.get('allow_blank')) && Ember.isEmpty(this.get('value'))) { return true; }
return false;
}.property('saving', 'value'),

View File

@ -32,7 +32,7 @@ export default Ember.ArrayController.extend({
}
});
return badges;
return _.sortBy(badges, "name");
}.property('badges.@each', 'model.@each'),
/**

View File

@ -17,15 +17,18 @@ export default Ember.ObjectController.extend(BufferedContent, {
if (this.get('required')) {
ret.push(I18n.t('admin.user_fields.required.enabled'));
}
if (this.get('show_on_profile')) {
ret.push(I18n.t('admin.user_fields.show_on_profile.enabled'));
}
return ret.join(', ');
}.property('editable', 'required'),
}.property('editable', 'required', 'show_on_profile'),
actions: {
save: function() {
var self = this;
var attrs = this.get('buffered').getProperties('name', 'description', 'field_type', 'editable', 'required');
var attrs = this.get('buffered').getProperties('name', 'description', 'field_type', 'editable', 'required', 'show_on_profile');
this.get('model').save(attrs).then(function(res) {
self.set('model.id', res.user_field.id);
@ -50,7 +53,7 @@ export default Ember.ObjectController.extend(BufferedContent, {
cancel: function() {
var id = this.get('id');
if (Ember.empty(id)) {
if (Ember.isEmpty(id)) {
this.get('controllers.admin-user-fields').send('destroy', this.get('model'));
} else {
this.rollbackBuffer();

View File

@ -19,7 +19,7 @@ export default ObjectController.extend(CanCheckEmails, {
var siteUserFields = this.site.get('user_fields'),
userFields = this.get('user_fields');
if (!Ember.empty(siteUserFields)) {
if (!Ember.isEmpty(siteUserFields)) {
return siteUserFields.map(function(uf) {
var value = userFields ? userFields[uf.get('id').toString()] : null;
return {name: uf.get('name'), value: value};

View File

@ -0,0 +1,75 @@
export default Ember.ArrayController.extend({
query: null,
showEmails: false,
refreshing: false,
listFilter: null,
selectAll: false,
queryNew: Em.computed.equal('query', 'new'),
queryPending: Em.computed.equal('query', 'pending'),
queryHasApproval: Em.computed.or('queryNew', 'queryPending'),
showApproval: Em.computed.and('siteSettings.must_approve_users', 'queryHasApproval'),
searchHint: Discourse.computed.i18n('search_hint'),
hasSelection: Em.computed.gt('selectedCount', 0),
selectedCount: function() {
var model = this.get('model');
if (!model || !model.length) return 0;
return model.filterProperty('selected').length;
}.property('model.@each.selected'),
selectAllChanged: function() {
var val = this.get('selectAll');
this.get('model').forEach(function(user) {
if (user.get('can_approve')) {
user.set('selected', val);
}
});
}.observes('selectAll'),
title: function() {
return I18n.t('admin.users.titles.' + this.get('query'));
}.property('query'),
_filterUsers: Discourse.debounce(function() {
this._refreshUsers();
}, 250).observes('listFilter'),
_refreshUsers: function() {
var self = this;
this.set('refreshing', true);
Discourse.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);
});
},
actions: {
approveUsers: function() {
Discourse.AdminUser.bulkApprove(this.get('model').filterProperty('selected'));
this._refreshUsers();
},
rejectUsers: function() {
var maxPostAge = this.siteSettings.delete_user_max_post_age;
var controller = this;
Discourse.AdminUser.bulkReject(this.get('model').filterProperty('selected')).then(function(result){
var message = I18n.t("admin.users.reject_successful", {count: result.success});
if (result.failed > 0) {
message += ' ' + I18n.t("admin.users.reject_failures", {count: result.failed});
message += ' ' + I18n.t("admin.user.delete_forbidden", {count: maxPostAge});
}
bootbox.alert(message);
controller._refreshUsers();
});
},
showEmails: function() {
this.set('showEmails', true);
this._refreshUsers(true);
}
}
});

View File

@ -1,140 +0,0 @@
/**
This controller supports the interface for listing users in the admin section.
@class AdminUsersListController
@extends Ember.ArrayController
@namespace Discourse
@module Discourse
**/
export default Ember.ArrayController.extend(Discourse.Presence, {
username: null,
query: null,
selectAll: false,
loading: false,
mustApproveUsers: Discourse.computed.setting('must_approve_users'),
queryNew: Em.computed.equal('query', 'new'),
queryPending: Em.computed.equal('query', 'pending'),
queryHasApproval: Em.computed.or('queryNew', 'queryPending'),
searchHint: function() { return I18n.t("search_hint"); }.property(),
/**
Triggered when the selectAll property is changed
@event selectAll
**/
selectAllChanged: function() {
var _this = this;
_.each(this.get('model'),function(user) {
user.set('selected', _this.get('selectAll'));
});
}.observes('selectAll'),
/**
Triggered when the username filter is changed
@event filterUsers
**/
filterUsers: Discourse.debounce(function() {
this.refreshUsers();
}, 250).observes('username'),
/**
Triggered when the order of the users list is changed
@event orderChanged
**/
orderChanged: function() {
this.refreshUsers();
}.observes('query'),
/**
The title of the user list, based on which query was performed.
@property title
**/
title: function() {
return I18n.t('admin.users.titles.' + this.get('query'));
}.property('query'),
/**
Do we want to show the approval controls?
@property showApproval
**/
showApproval: function() {
return Discourse.SiteSettings.must_approve_users && this.get('queryHasApproval');
}.property('queryPending'),
/**
How many users are currently selected
@property selectedCount
**/
selectedCount: function() {
if (this.blank('model')) return 0;
return this.get('model').filterProperty('selected').length;
}.property('model.@each.selected'),
/**
Do we have any selected users?
@property hasSelection
**/
hasSelection: Em.computed.gt('selectedCount', 0),
/**
Refresh the current list of users.
@method refreshUsers
**/
refreshUsers: function(showEmails) {
var adminUsersListController = this;
adminUsersListController.set('loading', true);
Discourse.AdminUser.findAll(this.get('query'), { filter: this.get('username'), show_emails: showEmails }).then(function (result) {
adminUsersListController.set('model', result);
adminUsersListController.set('loading', false);
});
},
/**
Show the list of users.
@method show
**/
show: function(term) {
if (this.get('query') === term) {
this.refreshUsers();
return;
}
this.set('query', term);
},
actions: {
approveUsers: function() {
Discourse.AdminUser.bulkApprove(this.get('model').filterProperty('selected'));
this.refreshUsers();
},
rejectUsers: function() {
var controller = this;
Discourse.AdminUser.bulkReject(this.get('model').filterProperty('selected')).then(function(result){
var message = I18n.t("admin.users.reject_successful", {count: result.success});
if (result.failed > 0) {
message += ' ' + I18n.t("admin.users.reject_failures", {count: result.failed});
message += ' ' + I18n.t("admin.user.delete_forbidden", {count: Discourse.SiteSettings.delete_user_max_post_age});
}
bootbox.alert(message);
controller.refreshUsers();
});
},
showEmails: function() {
this.refreshUsers(true);
}
}
});

View File

@ -2,6 +2,6 @@ import DiscourseController from 'discourse/controllers/controller';
export default DiscourseController.extend({
showBadges: function() {
return this.get('currentUser.admin') && Discourse.SiteSettings.enable_badges;
return this.get('currentUser.admin') && this.siteSettings.enable_badges;
}.property()
});

View File

@ -1,17 +0,0 @@
/**
Return the count of users at the given trust level.
@method valueAtTrustLevel
@for Handlebars
**/
Handlebars.registerHelper('valueAtTrustLevel', function(property, trustLevel) {
var data = Ember.Handlebars.get(this, property);
if( data ) {
var item = data.find( function(d) { return parseInt(d.x,10) === parseInt(trustLevel,10); } );
if( item ) {
return item.y;
} else {
return 0;
}
}
});

View File

@ -0,0 +1,13 @@
import registerUnbound from 'discourse/helpers/register-unbound';
registerUnbound('value-at-tl', function(data, params) {
var tl = parseInt(params.level, 10);
if (data) {
var item = data.find( function(d) { return parseInt(d.x, 10) === tl; } );
if (item) {
return item.y;
} else {
return 0;
}
}
});

View File

@ -98,7 +98,21 @@ Discourse.AdminUser = Discourse.User.extend({
this.set('admin', true);
this.set('can_grant_admin', false);
this.set('can_revoke_admin', true);
Discourse.ajax("/admin/users/" + (this.get('id')) + "/grant_admin", {type: 'PUT'});
var self = this;
Discourse.ajax("/admin/users/" + (this.get('id')) + "/grant_admin", {type: 'PUT'})
.then(null, function(e) {
self.set('admin', false);
self.set('can_grant_admin', true);
self.set('can_revoke_admin', false);
var error;
if (e.responseJSON && e.responseJSON.error) {
error = e.responseJSON.error;
}
error = error || I18n.t('admin.user.grant_admin_failed', { error: "http: " + e.status + " - " + e.body });
bootbox.alert(error);
});
},
// Revoke the user's moderation access
@ -113,7 +127,20 @@ Discourse.AdminUser = Discourse.User.extend({
this.set('moderator', true);
this.set('can_grant_moderation', false);
this.set('can_revoke_moderation', true);
Discourse.ajax("/admin/users/" + (this.get('id')) + "/grant_moderation", {type: 'PUT'});
var self = this;
Discourse.ajax("/admin/users/" + (this.get('id')) + "/grant_moderation", {type: 'PUT'})
.then(null, function(e) {
self.set('moderator', false);
self.set('can_grant_moderation', true);
self.set('can_revoke_moderation', false);
var error;
if (e.responseJSON && e.responseJSON.error) {
error = e.responseJSON.error;
}
error = error || I18n.t('admin.user.grant_moderation_failed', { error: "http: " + e.status + " - " + e.body });
bootbox.alert(error);
});
},
refreshBrowsers: function() {
@ -298,9 +325,7 @@ Discourse.AdminUser = Discourse.User.extend({
});
},
deleteForbidden: function() {
return (!this.get('can_be_deleted') || this.get('post_count') > 0);
}.property('post_count'),
deleteForbidden: Em.computed.not("canBeDeleted"),
deleteExplanation: function() {
if (this.get('deleteForbidden')) {
@ -316,9 +341,10 @@ Discourse.AdminUser = Discourse.User.extend({
destroy: function(opts) {
var user = this;
var location = document.location.pathname;
var performDestroy = function(block) {
var formData = { context: window.location.pathname };
var formData = { context: location };
if (block) {
formData["block_email"] = true;
formData["block_urls"] = true;
@ -332,7 +358,11 @@ Discourse.AdminUser = Discourse.User.extend({
data: formData
}).then(function(data) {
if (data.deleted) {
document.location = "/admin/users/list/active";
if (/^\/admin\/users\/list\//.test(location)) {
document.location = location;
} else {
document.location = "/admin/users/list/active";
}
} else {
bootbox.alert(I18n.t("admin.user.delete_failed"));
if (data.user) {

View File

@ -1,26 +0,0 @@
/**
Data model for representing an export
@class ExportCsv
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.ExportCsv = Discourse.Model.extend({});
Discourse.ExportCsv.reopenClass({
/**
Exports user list
@method export_user_list
**/
exportUserList: function() {
return Discourse.ajax("/admin/export_csv/users.json").then(function(result) {
if (result.success) {
bootbox.alert(I18n.t("admin.export_csv.success"));
} else {
bootbox.alert(I18n.t("admin.export_csv.failed"));
}
});
}
});

View File

@ -45,11 +45,15 @@ Discourse.ScreenedIpAddress = Discourse.Model.extend({
});
Discourse.ScreenedIpAddress.reopenClass({
findAll: function() {
return Discourse.ajax("/admin/logs/screened_ip_addresses.json").then(function(screened_ips) {
findAll: function(filter) {
return Discourse.ajax("/admin/logs/screened_ip_addresses.json", { data: { filter: filter } }).then(function(screened_ips) {
return screened_ips.map(function(b) {
return Discourse.ScreenedIpAddress.create(b);
});
});
},
rollUp: function() {
return Discourse.ajax("/admin/logs/screened_ip_addresses/roll_up", { type: "POST" });
}
});

View File

@ -7,11 +7,16 @@
@module Discourse
**/
Discourse.SiteCustomization = Discourse.Model.extend({
trackedProperties: ['enabled', 'name', 'stylesheet', 'header', 'footer', 'mobile_stylesheet', 'mobile_header', 'mobile_footer', 'override_default_style'],
trackedProperties: [
'enabled', 'name',
'stylesheet', 'header', 'top', 'footer',
'mobile_stylesheet', 'mobile_header', 'mobile_top', 'mobile_footer',
'head_tag', 'body_tag'
],
description: function() {
return "" + this.name + (this.enabled ? ' (*)' : '');
}.property('selected', 'name'),
}.property('selected', 'name', 'enabled'),
changed: function() {
var self = this;
@ -25,8 +30,10 @@ Discourse.SiteCustomization = Discourse.Model.extend({
if (changed) { this.set('savingStatus', ''); }
return changed;
}.property('override_default_style', 'enabled', 'name', 'stylesheet', 'header', 'footer', 'mobile_stylesheet', 'mobile_header', 'mobile_footer', 'originals'),
}.property('enabled', 'name', 'originals',
'stylesheet', 'header', 'top', 'footer',
'mobile_stylesheet', 'mobile_header', 'mobile_top', 'mobile_footer',
'head_tag', 'body_tag'),
startTrackingChanges: function() {
var self = this;
@ -43,16 +50,20 @@ Discourse.SiteCustomization = Discourse.Model.extend({
save: function() {
this.set('savingStatus', I18n.t('saving'));
this.set('saving',true);
var data = {
name: this.name,
enabled: this.enabled,
stylesheet: this.stylesheet,
header: this.header,
top: this.top,
footer: this.footer,
mobile_stylesheet: this.mobile_stylesheet,
mobile_header: this.mobile_header,
mobile_top: this.mobile_top,
mobile_footer: this.mobile_footer,
override_default_style: this.override_default_style
head_tag: this.head_tag,
body_tag: this.body_tag
};
var siteCustomization = this;

View File

@ -1,11 +1,3 @@
/**
Our data model for interacting with site settings.
@class SiteSetting
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.SiteSetting = Discourse.Model.extend({
validationMessage: null,
@ -17,16 +9,13 @@ Discourse.SiteSetting = Discourse.Model.extend({
**/
enabled: function(key, value) {
if (arguments.length === 1) {
// get the boolean value of the setting
if (this.blank('value')) return false;
return this.get('value') === 'true';
} else {
// set the boolean value of the setting
if (arguments.length > 1) {
this.set('value', value ? 'true' : 'false');
}
if (this.blank('value')) return false;
return this.get('value') === 'true';
}.property('value'),
/**

View File

@ -1,11 +1,3 @@
/**
Represents an action taken by a staff member that has been logged.
@class StaffActionLog
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.StaffActionLog = Discourse.Model.extend({
showFullDetails: false,

View File

@ -1,8 +1,3 @@
var _fieldTypes = [
Ember.Object.create({id: 'text', name: I18n.t('admin.user_fields.field_types.text') }),
Ember.Object.create({id: 'confirm', name: I18n.t('admin.user_fields.field_types.confirm') })
];
var UserField = Ember.Object.extend({
destroy: function() {
var self = this;
@ -43,11 +38,18 @@ UserField.reopenClass({
},
fieldTypes: function() {
return _fieldTypes;
if (!this._fieldTypes) {
this._fieldTypes = [
Ember.Object.create({id: 'text', name: I18n.t('admin.user_fields.field_types.text') }),
Ember.Object.create({id: 'confirm', name: I18n.t('admin.user_fields.field_types.confirm') })
];
}
return this._fieldTypes;
},
fieldTypeById: function(id) {
return _fieldTypes.findBy('id', id);
return this.fieldTypes().findBy('id', id);
}
});

View File

@ -1,12 +1,3 @@
/**
Handles the default admin route
@class AdminDashboardRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
export default Discourse.Route.extend({
setupController: function(c) {
@ -16,8 +7,9 @@ export default Discourse.Route.extend({
fetchDashboardData: function(c) {
if( !c.get('dashboardFetchedAt') || moment().subtract(30, 'minutes').toDate() > c.get('dashboardFetchedAt') ) {
c.set('dashboardFetchedAt', new Date());
var versionChecks = this.siteSettings.version_checks;
Discourse.AdminDashboard.find().then(function(d) {
if( Discourse.SiteSettings.version_checks ){
if (versionChecks) {
c.set('versionCheck', Discourse.VersionCheck.create(d.version_check));
}
_.each(d.reports,function(report){
@ -32,7 +24,8 @@ export default Discourse.Route.extend({
c.set('top_referrers', topReferrers);
}
['admins', 'moderators', 'blocked', 'suspended', 'top_traffic_sources', 'top_referred_topics', 'updated_at'].forEach(function(x) {
[ 'disk_space','admins', 'moderators', 'blocked', 'suspended',
'top_traffic_sources', 'top_referred_topics', 'updated_at'].forEach(function(x) {
c.set(x, d[x]);
});

View File

@ -0,0 +1,7 @@
export default Discourse.Route.extend({
model: function() {
return Discourse.ajax("/admin/customize/emojis.json").then(function(emojis) {
return emojis.map(function (emoji) { return Ember.Object.create(emoji); });
});
}
});

View File

@ -0,0 +1,20 @@
export default Discourse.Route.extend({
model: function(params) {
var groups = this.modelFor('adminGroupsType'),
group = groups.findProperty('name', params.name);
if (!group) { return this.transitionTo('adminGroups.index'); }
return group;
},
setupController: function(controller, model) {
controller.set("model", model);
// clear the user selector
controller.set("usernames", null);
// load the members of the group
model.findMembers();
}
});

View File

@ -0,0 +1,5 @@
export default Discourse.Route.extend({
redirect: function() {
this.transitionTo("adminGroupsType", "custom");
}
});

View File

@ -0,0 +1,17 @@
export default Discourse.Route.extend({
model(params) {
return Discourse.Group.findAll().then(function(groups) {
return groups.filterBy("type", params.type);
});
},
actions: {
newGroup() {
const self = this;
this.transitionTo("adminGroupsType", "custom").then(function() {
var group = Discourse.Group.create({ automatic: false, visible: true });
self.transitionTo("adminGroup", group);
});
}
}
});

View File

@ -0,0 +1,23 @@
export default Discourse.Route.extend({
// TODO: make this automatic using an `{{outlet}}`
renderTemplate: function() {
this.render('admin/templates/logs/staff_action_logs', {into: 'adminLogs'});
},
setupController: function(controller) {
controller.resetFilters();
controller.refresh();
},
actions: {
showDetailsModal: function(logRecord) {
Discourse.Route.showModal(this, 'admin_staff_action_log_details', logRecord);
this.controllerFor('modal').set('modalClass', 'log-details-modal');
},
showCustomDetailsModal: function(logRecord) {
Discourse.Route.showModal(this, logRecord.action_name + '_details', logRecord);
this.controllerFor('modal').set('modalClass', 'tabbed-modal log-details-modal');
}
}
});

View File

@ -0,0 +1,12 @@
export default Ember.Route.extend({
model() {
return this.store.findAll('plugin');
},
actions: {
showSettings() {
this.transitionTo('adminSiteSettingsCategory', 'plugins');
}
}
});

View File

@ -0,0 +1,66 @@
export default {
resource: 'admin',
map() {
this.route('dashboard', { path: '/' });
this.resource('adminSiteSettings', { path: '/site_settings' }, function() {
this.resource('adminSiteSettingsCategory', { path: 'category/:category_id'} );
});
this.resource('adminEmail', { path: '/email'}, function() {
this.route('all');
this.route('sent');
this.route('skipped');
this.route('previewDigest', { path: '/preview-digest' });
});
this.resource('adminCustomize', { path: '/customize' } ,function() {
this.route('colors');
this.route('css_html');
this.resource('adminSiteText', { path: '/site_text' }, function() {
this.route('edit', {path: '/:text_type'});
});
this.resource('adminUserFields', { path: '/user_fields' });
this.resource('adminEmojis', { path: '/emojis' });
});
this.route('api');
this.resource('admin.backups', { path: '/backups' }, function() {
this.route('logs');
});
this.resource('adminReports', { path: '/reports/:type' });
this.resource('adminFlags', { path: '/flags' }, function() {
this.route('list', { path: '/:filter' });
});
this.resource('adminLogs', { path: '/logs' }, function() {
this.route('staffActionLogs', { path: '/staff_action_logs' });
this.route('screenedEmails', { path: '/screened_emails' });
this.route('screenedIpAddresses', { path: '/screened_ip_addresses' });
this.route('screenedUrls', { path: '/screened_urls' });
});
this.resource('adminGroups', { path: '/groups' }, function() {
this.resource('adminGroupsType', { path: '/:type' }, function() {
this.resource('adminGroup', { path: '/:name' });
});
});
this.resource('adminUsers', { path: '/users' }, function() {
this.resource('adminUser', { path: '/:username' }, function() {
this.route('badges');
this.route('tl3Requirements', { path: '/tl3_requirements' });
});
this.resource('adminUsersList', { path: '/list' }, function() {
this.route('show', { path: '/:filter' });
});
});
this.resource('adminBadges', { path: '/badges' }, function() {
this.route('show', { path: '/:badge_id' });
});
}
};

View File

@ -0,0 +1,5 @@
export default Discourse.Route.extend({
redirect: function() {
this.replaceWith('adminUsersList.show', 'active');
}
});

View File

@ -0,0 +1,15 @@
export default Discourse.Route.extend({
model: function(params) {
this.userFilter = params.filter;
return Discourse.AdminUser.findAll(params.filter);
},
setupController: function(controller, model) {
controller.setProperties({
model: model,
query: this.userFilter,
showEmails: false,
refreshing: false,
});
}
});

View File

@ -0,0 +1,19 @@
import { outputExportResult } from 'discourse/lib/export-result';
export default Discourse.Route.extend({
actions: {
exportUsers: function() {
Discourse.ExportCsv.exportUserList().then(outputExportResult);
},
sendInvites: function() {
this.transitionTo('user.invited', Discourse.User.current());
},
deleteUser: function(user) {
Discourse.AdminUser.create(user).destroy({ deletePosts: true });
}
}
});

View File

@ -1,13 +1,4 @@
/**
Handles email routes
@class AdminEmailRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminEmailIndexRoute = Discourse.Route.extend({
model: function() {
return Discourse.EmailSettings.find();
},

View File

@ -1,23 +0,0 @@
Discourse.AdminGroupRoute = Discourse.Route.extend({
model: function(params) {
var groups = this.modelFor('adminGroups'),
group = groups.findProperty('name', params.name);
if (!group) { return this.transitionTo('adminGroups.index'); }
return group;
},
afterModel: function(model) {
var self = this;
return model.findMembers().then(function(members) {
self.set('_members', members);
});
},
setupController: function(controller, model) {
controller.set('model', model);
controller.set('members', this.get('_members'));
}
});

View File

@ -1,31 +0,0 @@
/**
Handles routes for admin groups
@class AdminGroupsRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminGroupsRoute = Discourse.Route.extend({
model: function() {
return Discourse.Group.findAll();
},
actions: {
showGroup: function(g) {
// This hack is needed because the autocomplete plugin does not
// refresh properly when the underlying data changes. TODO should
// be to update the plugin so it works properly and remove this hack.
var self = this;
this.transitionTo('adminGroups.index').then(function() {
self.transitionTo('adminGroup', g);
});
},
newGroup: function(){
var group = Discourse.Group.create({ visible: true });
this.send('showGroup', group);
}
}
});

View File

@ -12,47 +12,6 @@ Discourse.AdminLogsIndexRoute = Discourse.Route.extend({
}
});
/**
The route that lists staff actions that were logged.
@class AdminLogsStaffActionLogsRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminLogsStaffActionLogsRoute = Discourse.Route.extend({
renderTemplate: function() {
this.render('admin/templates/logs/staff_action_logs', {into: 'adminLogs'});
},
setupController: function(controller) {
var queryParams = Discourse.URL.get('queryParams');
if (queryParams) {
controller.set('filters', queryParams);
}
return controller.show();
},
actions: {
showDetailsModal: function(logRecord) {
Discourse.Route.showModal(this, 'admin_staff_action_log_details', logRecord);
this.controllerFor('modal').set('modalClass', 'log-details-modal');
},
showCustomDetailsModal: function(logRecord) {
Discourse.Route.showModal(this, logRecord.action_name + '_details', logRecord);
this.controllerFor('modal').set('modalClass', 'tabbed-modal log-details-modal');
}
},
deactivate: function() {
this._super();
// Clear any filters when we leave the route
Discourse.URL.set('queryParams', null);
}
});
/**
The route that lists blocked email addresses.

View File

@ -1,65 +0,0 @@
Discourse.Route.buildRoutes(function() {
this.resource('admin', function() {
this.route('dashboard', { path: '/' });
this.resource('adminSiteSettings', { path: '/site_settings' }, function() {
this.resource('adminSiteSettingsCategory', { path: 'category/:category_id'} );
});
this.resource('adminEmail', { path: '/email'}, function() {
this.route('all');
this.route('sent');
this.route('skipped');
this.route('previewDigest', { path: '/preview-digest' });
});
this.resource('adminCustomize', { path: '/customize' } ,function() {
this.route('colors');
this.route('css_html');
this.resource('adminSiteText', { path: '/site_text' }, function() {
this.route('edit', {path: '/:text_type'});
});
this.resource('adminUserFields', { path: '/user_fields' }, function() {
});
});
this.route('api');
this.resource('admin.backups', { path: '/backups' }, function() {
this.route('logs');
});
this.resource('adminReports', { path: '/reports/:type' });
this.resource('adminFlags', { path: '/flags' }, function() {
this.route('list', { path: '/:filter' });
});
this.resource('adminLogs', { path: '/logs' }, function() {
this.route('staffActionLogs', { path: '/staff_action_logs' });
this.route('screenedEmails', { path: '/screened_emails' });
this.route('screenedIpAddresses', { path: '/screened_ip_addresses' });
this.route('screenedUrls', { path: '/screened_urls' });
});
this.resource('adminGroups', { path: '/groups'}, function() {
this.resource('adminGroup', { path: '/:name' });
});
this.resource('adminUsers', { path: '/users' }, function() {
this.resource('adminUser', { path: '/:username' }, function() {
this.route('badges');
this.route('tl3Requirements', { path: '/tl3_requirements' });
});
this.resource('adminUsersList', { path: '/list' }, function() {
_.each(['active', 'new', 'pending', 'admins', 'moderators', 'blocked', 'suspended',
'newuser', 'basicuser', 'regular', 'leaders', 'elders'], function(x) {
this.route(x, { path: '/' + x });
}, this);
});
});
this.resource('adminBadges', { path: '/badges' }, function() {
this.route('show', { path: '/:badge_id' });
});
});
});

View File

@ -1,131 +0,0 @@
/**
Handles the route that deals with listing users
@class AdminUsersListRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminUsersListRoute = Discourse.Route.extend({
renderTemplate: function() {
this.render('admin/templates/users_list', {into: 'admin/templates/admin'});
},
actions: {
exportUsers: function() {
Discourse.ExportCsv.exportUserList();
}
}
});
/**
Index should just redirect to active
@class AdminUsersIndexRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminUsersListIndexRoute = Discourse.Route.extend({
redirect: function() {
this.transitionTo('adminUsersList.active');
}
});
/**
Handles the route that lists active users.
@class AdminUsersListActiveRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminUsersListActiveRoute = Discourse.Route.extend({
setupController: function() {
return this.controllerFor('adminUsersList').show('active');
}
});
/**
Handles the route that lists new users.
@class AdminUsersListNewRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminUsersListNewRoute = Discourse.Route.extend({
setupController: function() {
return this.controllerFor('adminUsersList').show('new');
}
});
/**
Handles the route that lists pending users.
@class AdminUsersListPendingRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminUsersListPendingRoute = Discourse.Route.extend({
setupController: function() {
return this.controllerFor('adminUsersList').show('pending');
}
});
/**
Handles the route that lists admin users.
@class AdminUsersListAdminsRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminUsersListAdminsRoute = Discourse.Route.extend({
setupController: function() {
return this.controllerFor('adminUsersList').show('admins');
}
});
/**
Handles the route that lists moderators.
@class AdminUsersListModeratorsRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminUsersListModeratorsRoute = Discourse.Route.extend({
setupController: function() {
return this.controllerFor('adminUsersList').show('moderators');
}
});
/**
Handles the route that lists blocked users.
@class AdminUsersListBlockedRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminUsersListBlockedRoute = Discourse.Route.extend({
setupController: function() {
return this.controllerFor('adminUsersList').show('blocked');
}
});
/**
Handles the route that lists suspended users.
@class AdminUsersListSuspendedRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminUsersListSuspendedRoute = Discourse.Route.extend({
setupController: function() {
return this.controllerFor('adminUsersList').show('suspended');
}
});

View File

@ -1,69 +0,0 @@
/**
Handles the route that lists users at trust level 0.
@class AdminUsersListNewuserRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminUsersListNewuserRoute = Discourse.Route.extend({
setupController: function() {
return this.controllerFor('adminUsersList').show('newuser');
}
});
/**
Handles the route that lists users at trust level 1.
@class AdminUsersListBasicuserRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminUsersListBasicuserRoute = Discourse.Route.extend({
setupController: function() {
return this.controllerFor('adminUsersList').show('basic');
}
});
/**
Handles the route that lists users at trust level 2.
@class AdminUsersListRegularRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminUsersListRegularRoute = Discourse.Route.extend({
setupController: function() {
return this.controllerFor('adminUsersList').show('regular');
}
});
/**
Handles the route that lists users at trust level 3.
@class AdminUsersListLeadersRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminUsersListLeadersRoute = Discourse.Route.extend({
setupController: function() {
return this.controllerFor('adminUsersList').show('leader');
}
});
/**
Handles the route that lists users at trust level 4.
@class AdminUsersListEldersRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.AdminUsersListEldersRoute = Discourse.Route.extend({
setupController: function() {
return this.controllerFor('adminUsersList').show('elder');
}
});

View File

@ -4,25 +4,27 @@
<div class="full-width">
<ul class="nav nav-pills">
<li>{{#link-to 'admin.dashboard'}}{{i18n admin.dashboard.title}}{{/link-to}}</li>
{{admin-nav-item route='admin.dashboard' label='admin.dashboard.title'}}
{{#if currentUser.admin}}
<li>{{#link-to 'adminSiteSettings'}}{{i18n admin.site_settings.title}}{{/link-to}}</li>
{{admin-nav-item route='adminSiteSettings' label='admin.site_settings.title'}}
{{/if}}
<li>{{#link-to 'adminUsersList'}}{{i18n admin.users.title}}{{/link-to}}</li>
{{admin-nav-item route='adminUsersList' label='admin.users.title'}}
{{#if showBadges}}
<li>{{#link-to 'adminBadges.index'}}{{i18n admin.badges.title}}{{/link-to}}</li>
{{admin-nav-item route='adminBadges.index' label='admin.badges.title'}}
{{/if}}
{{#if currentUser.admin}}
<li>{{#link-to 'adminGroups.index'}}{{i18n admin.groups.title}}{{/link-to}}</li>
{{admin-nav-item route='adminGroups' label='admin.groups.title'}}
{{/if}}
<li>{{#link-to 'adminEmail'}}{{i18n admin.email.title}}{{/link-to}}</li>
<li>{{#link-to 'adminFlags'}}{{i18n admin.flags.title}}{{/link-to}}</li>
<li>{{#link-to 'adminLogs'}}{{i18n admin.logs.title}}{{/link-to}}</li>
{{admin-nav-item route='adminEmail' label='admin.email.title'}}
{{admin-nav-item route='adminFlags' label='admin.flags.title'}}
{{admin-nav-item route='adminLogs' label='admin.logs.title'}}
{{#if currentUser.admin}}
<li>{{#link-to 'adminCustomize.colors'}}{{i18n admin.customize.title}}{{/link-to}}</li>
<li>{{#link-to 'admin.api'}}{{i18n admin.api.title}}{{/link-to}}</li>
<li>{{#link-to 'admin.backups'}}{{i18n admin.backups.title}}{{/link-to}}</li>
{{admin-nav-item route='adminCustomize.colors' label='admin.customize.title'}}
{{admin-nav-item route='admin.api' label='admin.api.title'}}
{{admin-nav-item route='admin.backups' label='admin.backups.title'}}
{{/if}}
{{admin-nav-item route='adminPlugins' label='admin.plugins.title'}}
{{plugin-outlet "admin-menu" tagName="li"}}
</ul>
<div class='boxed white admin-content'>
@ -34,4 +36,3 @@
</div>
</div>
</div>

View File

@ -1,33 +1,33 @@
{{#if model}}
<table class='api-keys'>
<tr>
<th>{{i18n admin.api.key}}</th>
<th>{{i18n admin.api.user}}</th>
<th>{{i18n 'admin.api.key'}}</th>
<th>{{i18n 'admin.api.user'}}</th>
<th>&nbsp;</th>
</tr>
{{#each model}}
{{#each k in model}}
<tr>
<td class='key'>{{key}}</td>
<td class='key'>{{k.key}}</td>
<td>
{{#if user}}
{{#link-to 'adminUser' user}}
{{avatar user imageSize="small"}}
{{#if k.user}}
{{#link-to 'adminUser' k.user}}
{{avatar k.user imageSize="small"}}
{{/link-to}}
{{else}}
{{i18n admin.api.all_users}}
{{i18n 'admin.api.all_users'}}
{{/if}}
</td>
<td>
<button class='btn' {{action "regenerateKey" this}}><i class="fa fa-undo"></i>{{i18n admin.api.regenerate}}</button>
<button class='btn' {{action "revokeKey" this}}><i class="fa fa-times"></i>{{i18n admin.api.revoke}}</button>
<button class='btn' {{action "regenerateKey" k}}><i class="fa fa-undo"></i>{{i18n 'admin.api.regenerate'}}</button>
<button class='btn' {{action "revokeKey" k}}><i class="fa fa-times"></i>{{i18n 'admin.api.revoke'}}</button>
</td>
</tr>
{{/each}}
</table>
{{else}}
<p>{{i18n admin.api.none}}</p>
<p>{{i18n 'admin.api.none'}}</p>
{{/if}}
{{#unless hasMasterKey}}
<button class='btn' {{action "generateMasterKey"}}><i class="fa fa-key"></i>{{i18n admin.api.generate_master}}</button>
<button class='btn' {{action "generateMasterKey"}}><i class="fa fa-key"></i>{{i18n 'admin.api.generate_master'}}</button>
{{/unless }}

View File

@ -1,18 +1,31 @@
<div class="admin-controls">
<div class="span15">
<ul class="nav nav-pills">
<li>{{#link-to "admin.backups.index"}}{{i18n admin.backups.menu.backups}}{{/link-to}}</li>
<li>{{#link-to "admin.backups.logs"}}{{i18n admin.backups.menu.logs}}{{/link-to}}</li>
{{admin-nav-item route='admin.backups.index' label='admin.backups.menu.backups'}}
{{admin-nav-item route='admin.backups.logs' label='admin.backups.menu.logs'}}
</ul>
</div>
<div class="pull-right">
{{#if canRollback}}
<button {{action "rollback"}} class="btn btn-rollback" title="{{i18n admin.backups.operations.rollback.title}}" {{bind-attr disabled="rollbackDisabled"}}><i class="fa fa-ambulance fa-flip-horizontal"></i>{{i18n admin.backups.operations.rollback.text}}</button>
{{d-button action="rollback"
class="btn-rollback"
label="admin.backups.operations.rollback.text"
title="admin.backups.operations.rollback.title"
icon="ambulance"
disabled=rollbackDisabled}}
{{/if}}
{{#if isOperationRunning}}
<button {{action "cancelOperation"}} class="btn btn-danger" title="{{i18n admin.backups.operations.cancel.title}}"><i class="fa fa-times"></i>{{i18n admin.backups.operations.cancel.text}}</button>
{{d-button action="cancelOperation"
class="btn-danger"
title="admin.backups.operations.cancel.title"
label="admin.backups.operations.cancel.text"
icon="times"}}
{{else}}
<button {{action "startBackup"}} class="btn btn-primary" title="{{i18n admin.backups.operations.backup.title}}"><i class="fa fa-rocket"></i>{{i18n admin.backups.operations.backup.text}}</button>
{{d-button action="startBackup"
class="btn-primary"
title="admin.backups.operations.backup.title"
label="admin.backups.operations.backup.text"
icon="rocket"}}
{{/if}}
</div>
</div>

View File

@ -1,7 +1,7 @@
<table>
<tr>
<th width="55%">{{i18n admin.backups.columns.filename}}</th>
<th width="10%">{{i18n admin.backups.columns.size}}</th>
<th width="55%">{{i18n 'admin.backups.columns.filename'}}</th>
<th width="10%">{{i18n 'admin.backups.columns.size'}}</th>
<th>
<div class="pull-right">
{{resumable-upload target="/admin/backups/upload" success="uploadSuccess" error="uploadError" uploadText=uploadText}}
@ -15,15 +15,15 @@
<td>{{human-size backup.size}}</td>
<td>
<div class="pull-right">
<a {{bind-attr href="backup.link"}} class="btn download" title="{{i18n admin.backups.operations.download.title}}"><i class="fa fa-download"></i>{{i18n admin.backups.operations.download.text}}</a>
<a {{bind-attr href="backup.link"}} class="btn download" title="{{i18n 'admin.backups.operations.download.title'}}"><i class="fa fa-download"></i>{{i18n 'admin.backups.operations.download.text'}}</a>
<button {{action "destroyBackup" backup}} class="btn btn-danger no-text" {{bind-attr disabled="destroyDisabled" title="destroyTitle"}}><i class="fa fa-trash-o"></i></button>
<button {{action "startRestore" backup}} class="btn" {{bind-attr disabled="restoreDisabled" title="restoreTitle"}}><i class="fa fa-play"></i>{{i18n admin.backups.operations.restore.text}}</button>
<button {{action "startRestore" backup}} class="btn" {{bind-attr disabled="restoreDisabled" title="restoreTitle"}}><i class="fa fa-play"></i>{{i18n 'admin.backups.operations.restore.text'}}</button>
</div>
</td>
</tr>
{{else}}
<tr>
<td>{{i18n admin.backups.none}}</td>
<td>{{i18n 'admin.backups.none'}}</td>
<td></td>
<td></td>
</tr>

View File

@ -1,9 +1,9 @@
<div class='span13'>
<p>{{i18n admin.badges.none_selected}}</p>
<p>{{i18n 'admin.badges.none_selected'}}</p>
<div>
{{#link-to 'adminBadges.show' 'new' class="btn"}}
{{fa-icon "plus"}} {{i18n admin.badges.new}}
{{fa-icon "plus"}} {{i18n 'admin.badges.new'}}
{{/link-to}}
</div>
</div>

View File

@ -1,52 +1,52 @@
<div class='current-badge span13'>
<form class="form-horizontal">
<div>
<label for="name">{{i18n admin.badges.name}}</label>
<label for="name">{{i18n 'admin.badges.name'}}</label>
{{input type="text" name="name" value=buffered.name}}
</div>
{{#if showDisplayName}}
<div>
<strong>{{i18n admin.badges.display_name}}</strong>
<strong>{{i18n 'admin.badges.display_name'}}</strong>
{{buffered.displayName}}
</div>
{{/if}}
<div>
<label for="name">{{i18n admin.badges.icon}}</label>
<label for="name">{{i18n 'admin.badges.icon'}}</label>
{{input type="text" name="name" value=buffered.icon}}
<p class='help'>{{i18n admin.badges.icon_help}}</p>
<p class='help'>{{i18n 'admin.badges.icon_help'}}</p>
</div>
<div>
<label for="name">{{i18n admin.badges.image}}</label>
<label for="name">{{i18n 'admin.badges.image'}}</label>
{{input type="text" name="name" value=buffered.image}}
<p class='help'>{{i18n admin.badges.icon_help}}</p>
<p class='help'>{{i18n 'admin.badges.icon_help'}}</p>
</div>
<div>
<label for="badge_type_id">{{i18n admin.badges.badge_type}}</label>
{{view Ember.Select name="badge_type_id"
value=buffered.badge_type_id
content=badgeTypes
optionValuePath="content.id"
optionLabelPath="content.name"
disabled=readOnly}}
<label for="badge_type_id">{{i18n 'admin.badges.badge_type'}}</label>
{{view "select" name="badge_type_id"
value=buffered.badge_type_id
content=badgeTypes
optionValuePath="content.id"
optionLabelPath="content.name"
disabled=readOnly}}
</div>
<div>
<label for="badge_grouping_id">{{i18n admin.badges.badge_grouping}}</label>
{{view Ember.Select name="badge_grouping_id"
value=buffered.badge_grouping_id
content=badgeGroupings
optionValuePath="content.id"
optionLabelPath="content.name"}}
<label for="badge_grouping_id">{{i18n 'admin.badges.badge_grouping'}}</label>
{{view "select" name="badge_grouping_id"
value=buffered.badge_grouping_id
content=badgeGroupings
optionValuePath="content.id"
optionLabelPath="content.name"}}
&nbsp;<button {{action "editGroupings"}} class='btn'>{{fa-icon 'pencil'}}</button>
</div>
<div>
<label for="description">{{i18n admin.badges.description}}</label>
<label for="description">{{i18n 'admin.badges.description'}}</label>
{{#if canEditDescription}}
{{textarea name="description" value=buffered.description}}
{{else}}
@ -55,83 +55,83 @@
</div>
<div>
<label for="query">{{i18n admin.badges.query}}</label>
<label for="query">{{i18n 'admin.badges.query'}}</label>
{{textarea name="query" value=buffered.query disabled=readOnly}}
</div>
{{#if hasQuery}}
<a href {{action "preview" buffered "false"}}>{{i18n admin.badges.preview.link_text}}</a>
<a href {{action "preview" buffered "false"}}>{{i18n 'admin.badges.preview.link_text'}}</a>
|
<a href {{action "preview" buffered "true"}}>{{i18n admin.badges.preview.plan_text}}</a>
<a href {{action "preview" buffered "true"}}>{{i18n 'admin.badges.preview.plan_text'}}</a>
{{#if preview_loading}}
{{i18n loading}}...
{{i18n 'loading'}}...
{{/if}}
<div>
<label>
{{input type="checkbox" checked=buffered.auto_revoke disabled=readOnly}}
{{i18n admin.badges.auto_revoke}}
{{i18n 'admin.badges.auto_revoke'}}
</label>
</div>
<div>
<label>
{{input type="checkbox" checked=buffered.target_posts disabled=readOnly}}
{{i18n admin.badges.target_posts}}
{{i18n 'admin.badges.target_posts'}}
</label>
</div>
<div>
<label for="trigger">{{i18n admin.badges.trigger}}</label>
{{view Ember.Select name="trigger"
value=buffered.trigger
content=badgeTriggers
optionValuePath="content.id"
optionLabelPath="content.name"
disabled=readOnly}}
<label for="trigger">{{i18n 'admin.badges.trigger'}}</label>
{{view "select" name="trigger"
value=buffered.trigger
content=badgeTriggers
optionValuePath="content.id"
optionLabelPath="content.name"
disabled=readOnly}}
</div>
{{/if}}
<div>
<label>
{{input type="checkbox" checked=buffered.allow_title}}
{{i18n admin.badges.allow_title}}
{{i18n 'admin.badges.allow_title'}}
</label>
</div>
<div>
<label>
{{input type="checkbox" checked=buffered.multiple_grant disabled=readOnly}}
{{i18n admin.badges.multiple_grant}}
{{i18n 'admin.badges.multiple_grant'}}
</label>
</div>
<div>
<label>
{{input type="checkbox" checked=buffered.listable disabled=readOnly}}
{{i18n admin.badges.listable}}
{{i18n 'admin.badges.listable'}}
</label>
</div>
<div>
<label>
{{input type="checkbox" checked=buffered.show_posts disabled=readOnly}}
{{i18n admin.badges.show_posts}}
{{i18n 'admin.badges.show_posts'}}
</label>
</div>
<div>
<label>
{{input type="checkbox" checked=buffered.enabled}}
{{i18n admin.badges.enabled}}
{{i18n 'admin.badges.enabled'}}
</label>
</div>
<div class='buttons'>
<button {{action "save"}} {{bind-attr disabled=saving}} class='btn btn-primary'>{{i18n admin.badges.save}}</button>
<button {{action "save"}} {{bind-attr disabled=saving}} class='btn btn-primary'>{{i18n 'admin.badges.save'}}</button>
<span class='saving'>{{savingStatus}}</span>
{{#unless readOnly}}
<a {{action "destroy"}} class='delete-link'>{{i18n admin.badges.delete}}</a>
<a {{action "destroy"}} class='delete-link'>{{i18n 'admin.badges.delete'}}</a>
{{/unless}}
</div>
</form>
@ -140,7 +140,7 @@
{{#if grant_count}}
<div class="span13 current-badge-actions">
<div>
{{#link-to 'badges.show' this}}{{i18n badges.granted count=grant_count}}{{/link-to}}
{{#link-to 'badges.show' this}}{{i18n 'badges.granted' count=grant_count}}{{/link-to}}
</div>
</div>
{{/if}}

View File

@ -1,21 +1,21 @@
<div class="badges">
<div class='content-list span6'>
<h3>{{i18n admin.badges.title}}</h3>
<h3>{{i18n 'admin.badges.title'}}</h3>
<ul>
{{#each}}
{{#each badge in model}}
<li>
{{#link-to 'adminBadges.show' id}}
{{badge-button badge=this}}
{{#if newBadge}}
<span class="list-badge">{{i18n filters.new.lower_title}}</span>
{{#link-to 'adminBadges.show' badge.id}}
{{badge-button badge=badge}}
{{#if badge.newBadge}}
<span class="list-badge">{{i18n 'filters.new.lower_title'}}</span>
{{/if}}
{{/link-to}}
</li>
{{/each}}
</ul>
{{#link-to 'adminBadges.show' 'new' class="btn"}}
{{fa-icon "plus"}} {{i18n admin.badges.new}}
{{fa-icon "plus"}} {{i18n 'admin.badges.new'}}
{{/link-to}}
<br>
<br>

View File

@ -0,0 +1,9 @@
{{#if routeParam}}
{{#link-to route routeParam}}{{i18n label}}{{/link-to}}
{{else}}
{{#if route}}
{{#link-to route}}{{i18n label}}{{/link-to}}
{{else}}
<a href="{{unbound href}}" data-auto-route="true">{{i18n label}}</a>
{{/if}}
{{/if}}

View File

@ -0,0 +1,7 @@
<div class='admin-controls'>
<div class='span15'>
<ul class="nav nav-pills">
{{yield}}
</ul>
</div>
</div>

View File

@ -1,13 +1,10 @@
<div class='admin-controls'>
<div class='span15'>
<ul class="nav nav-pills">
<li>{{#link-to 'adminCustomize.colors'}}{{i18n admin.customize.colors.title}}{{/link-to}}</li>
<li>{{#link-to 'adminCustomize.css_html'}}{{i18n admin.customize.css_html.title}}{{/link-to}}</li>
<li>{{#link-to 'adminSiteText'}}{{i18n admin.site_text.title}}{{/link-to}}</li>
<li>{{#link-to 'adminUserFields'}}{{i18n admin.user_fields.title}}{{/link-to}}</li>
</ul>
</div>
</div>
{{#admin-nav}}
{{admin-nav-item route='adminCustomize.colors' label='admin.customize.colors.title'}}
{{admin-nav-item route='adminCustomize.css_html' label='admin.customize.css_html.title'}}
{{admin-nav-item route='adminSiteText' label='admin.site_text.title'}}
{{admin-nav-item route='adminUserFields' label='admin.user_fields.title'}}
{{admin-nav-item route='adminEmojis' label='admin.emoji.title'}}
{{/admin-nav}}
<div class="admin-container">
{{outlet}}

View File

@ -1,35 +1,33 @@
<div class='content-list span6'>
<h3>{{i18n admin.customize.colors.long_title}}</h3>
<h3>{{i18n 'admin.customize.colors.long_title'}}</h3>
<ul>
{{#each model}}
{{#unless is_base}}
<li><a {{action "selectColorScheme" this}} {{bind-attr class="selected:active"}}>{{description}}</a></li>
{{#each scheme in model}}
{{#unless scheme.is_base}}
<li><a {{action "selectColorScheme" scheme}} {{bind-attr class="scheme.selected:active"}}>{{scheme.description}}</a></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'><i class="fa fa-plus"></i>{{i18n 'admin.customize.new'}}</button>
</div>
{{#if selectedItem}}
<div class="current-style color-scheme">
<div class="admin-container">
{{#with selectedItem}}
<h1>{{text-field class="style-name" value=name}}</h1>
<h1>{{text-field class="style-name" value=selectedItem.name}}</h1>
<div class="controls">
<button {{action "save"}} {{bind-attr disabled="disableSave"}} class='btn'>{{i18n admin.customize.save}}</button>
<button {{action "toggleEnabled"}} {{bind-attr disabled="disableEnable"}} class="btn">
{{#if enabled}}
{{i18n disable}}
{{else}}
{{i18n enable}}
{{/if}}
</button>
<button {{action "copy" this}} 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 {{bind-attr class=":saving savingStatus::hidden" }}>{{savingStatus}}</span>
</div>
{{/with}}
<div class="controls">
<button {{action "save"}} {{bind-attr disabled="selectedItem.disableSave"}} class='btn'>{{i18n 'admin.customize.save'}}</button>
<button {{action "toggleEnabled"}} {{bind-attr 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 {{bind-attr class=":saving selectedItem.savingStatus::hidden" }}>{{selectedItem.savingStatus}}</span>
</div>
<br/>
@ -37,7 +35,7 @@
<div class='search controls'>
<label>
{{input type="checkbox" checked=onlyOverridden}}
{{i18n admin.site_settings.show_overriden}}
{{i18n 'admin.site_settings.show_overriden'}}
</label>
</div>
</div>
@ -47,34 +45,34 @@
<thead>
<tr>
<th></th>
<th class="hex">{{i18n admin.customize.color}}</th>
<th class="hex">{{i18n 'admin.customize.color'}}</th>
<th></th>
</tr>
</thead>
<tbody>
{{#each colors}}
<tr {{bind-attr class="changed valid:valid:invalid"}}>
<td class="name" {{bind-attr title="name"}}>
<b>{{translatedName}}</b>
{{#each c in colors}}
<tr {{bind-attr class="c.changed c.valid:valid:invalid"}}>
<td class="name" {{bind-attr title="c.name"}}>
<b>{{c.translatedName}}</b>
<br/>
<span class="description">{{description}}</span>
<span class="description">{{c.description}}</span>
</td>
<td class="hex">{{color-input hexValue=hex brightnessValue=brightness valid=valid}}</td>
<td class="hex">{{color-input hexValue=c.hex brightnessValue=c.brightness valid=c.valid}}</td>
<td class="actions">
<button {{bind-attr class=":btn :revert savedIsOverriden::invisible"}} {{action "revert" this}} title="{{i18n admin.customize.colors.revert_title}}">{{i18n revert}}</button>
<button {{bind-attr class=":btn :undo changed::invisible"}} {{action "undo" this}} title="{{i18n admin.customize.colors.undo_title}}">{{i18n undo}}</button>
<button {{bind-attr class=":btn :revert c.savedIsOverriden::invisible"}} {{action "revert" c}} title="{{i18n 'admin.customize.colors.revert_title'}}">{{i18n 'revert'}}</button>
<button {{bind-attr class=":btn :undo 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>
<p>{{i18n 'search.no_results'}}</p>
{{/if}}
</div>
</div>
{{else}}
<p class="about">{{i18n admin.customize.colors.about}}</p>
<p class="about">{{i18n 'admin.customize.colors.about'}}</p>
{{/if}}
<div class="clearfix"></div>

View File

@ -1,59 +1,80 @@
<div class='content-list span6'>
<h3>{{i18n admin.customize.css_html.long_title}}</h3>
<h3>{{i18n 'admin.customize.css_html.long_title'}}</h3>
<ul>
{{#each model}}
<li><a {{action "selectStyle" this}} {{bind-attr class="this.selected:active"}}>{{this.description}}</a></li>
{{#each style in model}}
<li><a {{action "selectStyle" style}} {{bind-attr class="style.selected:active"}}>{{style.description}}</a></li>
{{/each}}
</ul>
<button {{action "newCustomization"}} class='btn'>
{{fa-icon "plus"}}{{i18n admin.customize.new}}
{{fa-icon "plus"}}{{i18n 'admin.customize.new'}}
</button>
</div>
{{#if selectedItem}}
<div class='current-style'>
{{#with selectedItem}}
{{text-field class="style-name" value=name}}
<div {{bind-attr class=":current-style view.maximized:maximized"}}>
<div class='wrapper'>
{{text-field class="style-name" value=selectedItem.name}}
<div class='admin-controls'>
<ul class="nav nav-pills">
<li><a {{bind-attr class="view.stylesheetActive:active"}} {{action "selectStylesheet" target="view"}}>{{i18n admin.customize.css}}</a></li>
<li><a {{bind-attr class="view.headerActive:active"}} {{action "selectHeader" target="view"}}>{{i18n admin.customize.header}}</a></li>
<li><a {{bind-attr class="view.footerActive:active"}} {{action "selectFooter" target="view"}}>{{i18n admin.customize.footer}}</a></li>
<li><a {{bind-attr class="view.mobileStylesheetActive:active"}} {{action "selectMobileStylesheet" target="view"}}>{{fa-icon "mobile"}}&nbsp;{{i18n admin.customize.css}}</a></li>
<li><a {{bind-attr class="view.mobileHeaderActive:active"}} {{action "selectMobileHeader" target="view"}}>{{fa-icon "mobile"}}&nbsp;{{i18n admin.customize.header}}</a></li>
<li><a {{bind-attr class="view.mobileFooterActive:active"}} {{action "selectMobileFooter" target="view"}}>{{fa-icon "mobile"}}&nbsp;{{i18n admin.customize.footer}}</a></li>
{{#if view.mobile}}
<li><a {{bind-attr class="view.mobileStylesheetActive:active"}} {{action "select" "mobile_stylesheet" target="view"}}>{{fa-icon "mobile"}}&nbsp;{{i18n 'admin.customize.css'}}</a></li>
<li><a {{bind-attr class="view.mobileHeaderActive:active"}} {{action "select" "mobile_header" target="view"}}>{{fa-icon "mobile"}}&nbsp;{{i18n 'admin.customize.header'}}</a></li>
<li><a {{bind-attr class="view.mobileTopActive:active"}} {{action "select" "mobile_top" target="view"}}>{{fa-icon "mobile"}}&nbsp;{{i18n 'admin.customize.top'}}</a></li>
<li><a {{bind-attr class="view.mobileFooterActive:active"}} {{action "select" "mobile_footer" target="view"}}>{{fa-icon "mobile"}}&nbsp;{{i18n 'admin.customize.footer'}}</a></li>
{{else}}
<li><a {{bind-attr class="view.stylesheetActive:active"}} {{action "select" "stylesheet" target="view"}}>{{i18n 'admin.customize.css'}}</a></li>
<li><a {{bind-attr class="view.headerActive:active"}} {{action "select" "header" target="view"}}>{{i18n 'admin.customize.header'}}</a></li>
<li><a {{bind-attr class="view.topActive:active"}} {{action "select" "top" target="view"}}>{{i18n 'admin.customize.top'}}</a></li>
<li><a {{bind-attr class="view.footerActive:active"}} {{action "select" "footer" target="view"}}>{{i18n 'admin.customize.footer'}}</a></li>
<li><a {{bind-attr class="view.headTagActive:active"}} {{action "select" "head_tag" target="view"}} title="{{i18n 'admin.customize.head_tag.title'}}">{{fa-icon "file-text-o"}}&nbsp;{{i18n 'admin.customize.head_tag.text'}}</a></li>
<li><a {{bind-attr class="view.bodyTagActive:active"}} {{action "select" "body_tag" target="view"}} title="{{i18n 'admin.customize.body_tag.title'}}">{{fa-icon "file-text-o"}}&nbsp;{{i18n 'admin.customize.body_tag.text'}}</a></li>
{{/if}}
<li class='toggle-mobile'>
<a {{bind-attr class="view.mobile:active"}} {{action "toggleMobile" target="view"}}>{{fa-icon "mobile"}}</a>
</li>
<li class='toggle-maximize'><a {{action "toggleMaximize" target="view"}}>
{{#if view.maximized}}
{{fa-icon "compress"}}
{{else}}
{{fa-icon "expand"}}
{{/if}}
</a>
</ul>
</div>
<div class="admin-container">
{{#if view.stylesheetActive}}{{aceEditor content=stylesheet mode="scss"}}{{/if}}
{{#if view.headerActive}}{{aceEditor content=header mode="html"}}{{/if}}
{{#if view.footerActive}}{{aceEditor content=footer mode="html"}}{{/if}}
{{#if view.mobileStylesheetActive}}{{aceEditor content=mobile_stylesheet mode="scss"}}{{/if}}
{{#if view.mobileHeaderActive}}{{aceEditor content=mobile_header mode="html"}}{{/if}}
{{#if view.mobileFooterActive}}{{aceEditor content=mobile_footer mode="html"}}{{/if}}
{{#if view.stylesheetActive}}{{aceEditor content=selectedItem.stylesheet mode="scss"}}{{/if}}
{{#if view.headerActive}}{{aceEditor content=selectedItem.header mode="html"}}{{/if}}
{{#if view.topActive}}{{aceEditor content=selectedItem.top mode="html"}}{{/if}}
{{#if view.footerActive}}{{aceEditor content=selectedItem.footer mode="html"}}{{/if}}
{{#if view.headTagActive}}{{aceEditor content=selectedItem.head_tag mode="html"}}{{/if}}
{{#if view.bodyTagActive}}{{aceEditor content=selectedItem.body_tag mode="html"}}{{/if}}
{{#if view.mobileStylesheetActive}}{{aceEditor content=selectedItem.mobile_stylesheet mode="scss"}}{{/if}}
{{#if view.mobileHeaderActive}}{{aceEditor content=selectedItem.mobile_header mode="html"}}{{/if}}
{{#if view.mobileTopActive}}{{aceEditor content=selectedItem.mobile_top mode="html"}}{{/if}}
{{#if view.mobileFooterActive}}{{aceEditor content=selectedItem.mobile_footer mode="html"}}{{/if}}
</div>
{{/with}}
<br>
<div class='status-actions'>
<span>{{i18n admin.customize.override_default}} {{view Ember.Checkbox checkedBinding="selectedItem.override_default_style"}}</span>
<span>{{i18n admin.customize.enabled}} {{view Ember.Checkbox checkedBinding="selectedItem.enabled"}}</span>
{{#unless selectedItem.changed}}
<a class='preview-link' {{bind-attr href="selectedItem.previewUrl"}} target='_blank' title="{{i18n admin.customize.explain_preview}}">{{i18n admin.customize.preview}}</a>
|
<a href="/?preview-style=" target='_blank' title="{{i18n admin.customize.explain_undo_preview}}">{{i18n admin.customize.undo_preview}}</a>
|
<a href="/?preview-style=default" target='_blank' title="{{i18n admin.customize.explain_rescue_preview}}">{{i18n admin.customize.rescue_preview}}</a><br>
{{/unless}}
</div>
<div class='admin-footer'>
<div class='status-actions'>
<span>{{i18n 'admin.customize.enabled'}} {{input type="checkbox" checked=selectedItem.enabled}}</span>
{{#unless selectedItem.changed}}
<a class='preview-link' {{bind-attr href="selectedItem.previewUrl"}} target='_blank' title="{{i18n 'admin.customize.explain_preview'}}">{{i18n 'admin.customize.preview'}}</a>
|
<a href="/?preview-style=" target='_blank' title="{{i18n 'admin.customize.explain_undo_preview'}}">{{i18n 'admin.customize.undo_preview'}}</a>
|
<a href="/?preview-style=default" target='_blank' title="{{i18n 'admin.customize.explain_rescue_preview'}}">{{i18n 'admin.customize.rescue_preview'}}</a><br>
{{/unless}}
</div>
<div class='buttons'>
<button {{action "save"}} {{bind-attr disabled="selectedItem.disableSave"}} class='btn'>{{i18n admin.customize.save}}</button>
<span class='saving'>{{selectedItem.savingStatus}}</span>
<a {{action "destroy"}} class='delete-link'>{{i18n admin.customize.delete}}</a>
<div class='buttons'>
<button {{action "save"}} {{bind-attr disabled="selectedItem.disableSave"}} class='btn'>{{i18n 'admin.customize.save'}}</button>
<span class='saving'>{{selectedItem.savingStatus}}</span>
<a {{action "destroy"}} class='delete-link'>{{i18n 'admin.customize.delete'}}</a>
</div>
</div>
</div>
</div>
{{else}}
<p class="about">{{i18n admin.customize.about}}</p>
<p class="about">{{i18n 'admin.customize.about'}}</p>
{{/if}}

View File

@ -24,16 +24,16 @@
<div class="dashboard-stats totals">
<table>
<tr>
<td class="title"><i class='fa fa-shield'></i> {{i18n admin.dashboard.admins}}</td>
<td class="value">{{#link-to 'adminUsersList.admins'}}{{admins}}{{/link-to}}</td>
<td class="title"><i class='fa fa-ban'></i> {{i18n admin.dashboard.suspended}}</td>
<td class="value">{{#link-to 'adminUsersList.suspended'}}{{suspended}}{{/link-to}}</td>
<td class="title"><i class='fa fa-shield'></i> {{i18n 'admin.dashboard.admins'}}</td>
<td class="value">{{#link-to 'adminUsersList.show' 'admins'}}{{admins}}{{/link-to}}</td>
<td class="title"><i class='fa fa-ban'></i> {{i18n 'admin.dashboard.suspended'}}</td>
<td class="value">{{#link-to 'adminUsersList.show' 'suspended'}}{{suspended}}{{/link-to}}</td>
</tr>
<tr>
<td class="title"><i class='fa fa-shield'></i> {{i18n admin.dashboard.moderators}}</td>
<td class="value">{{#link-to 'adminUsersList.moderators'}}{{moderators}}{{/link-to}}</td>
<td class="title"><i class='fa fa-ban'></i> {{i18n admin.dashboard.blocked}}</td>
<td class="value">{{#link-to 'adminUsersList.blocked'}}{{blocked}}{{/link-to}}</td>
<td class="title"><i class='fa fa-shield'></i> {{i18n 'admin.dashboard.moderators'}}</td>
<td class="value">{{#link-to 'adminUsersList.show' 'moderators'}}{{moderators}}{{/link-to}}</td>
<td class="title"><i class='fa fa-ban'></i> {{i18n 'admin.dashboard.blocked'}}</td>
<td class="value">{{#link-to 'adminUsersList.show' 'blocked'}}{{blocked}}{{/link-to}}</td>
</tr>
</table>
</div>
@ -43,21 +43,21 @@
<thead>
<tr>
<th>&nbsp;</th>
<th>{{i18n admin.dashboard.reports.today}}</th>
<th>{{i18n admin.dashboard.reports.yesterday}}</th>
<th>{{i18n admin.dashboard.reports.last_7_days}}</th>
<th>{{i18n admin.dashboard.reports.last_30_days}}</th>
<th>{{i18n admin.dashboard.reports.all}}</th>
<th>{{i18n 'admin.dashboard.reports.today'}}</th>
<th>{{i18n 'admin.dashboard.reports.yesterday'}}</th>
<th>{{i18n 'admin.dashboard.reports.last_7_days'}}</th>
<th>{{i18n 'admin.dashboard.reports.last_30_days'}}</th>
<th>{{i18n 'admin.dashboard.reports.all'}}</th>
</tr>
</thead>
{{#unless loading}}
{{ render 'admin/templates/reports/per_day_counts_report' visits tagName="tbody"}}
{{ render 'admin_report_counts' signups }}
{{ render 'admin_report_counts' topics }}
{{ render 'admin_report_counts' posts }}
{{ render 'admin_report_counts' likes }}
{{ render 'admin_report_counts' flags }}
{{ render 'admin_report_counts' bookmarks }}
{{ render 'admin_report_counts' starred }}
{{ render 'admin_report_counts' emails }}
{{/unless}}
</table>
@ -67,12 +67,34 @@
<table class="table table-condensed table-hover">
<thead>
<tr>
<th class="title" title="{{i18n admin.dashboard.private_messages_title}}"><i class="fa fa-envelope"></i> {{i18n admin.dashboard.private_messages_short}}</th>
<th>{{i18n admin.dashboard.reports.today}}</th>
<th>{{i18n admin.dashboard.reports.yesterday}}</th>
<th>{{i18n admin.dashboard.reports.last_7_days}}</th>
<th>{{i18n admin.dashboard.reports.last_30_days}}</th>
<th>{{i18n admin.dashboard.reports.all}}</th>
<th class="title" title="{{i18n 'admin.dashboard.page_views'}}">{{i18n 'admin.dashboard.page_views_short'}}</th>
<th>{{i18n 'admin.dashboard.reports.today'}}</th>
<th>{{i18n 'admin.dashboard.reports.yesterday'}}</th>
<th>{{i18n 'admin.dashboard.reports.last_7_days'}}</th>
<th>{{i18n 'admin.dashboard.reports.last_30_days'}}</th>
<th>{{i18n 'admin.dashboard.reports.all'}}</th>
</tr>
</thead>
{{#unless loading}}
{{ render 'admin_report_counts' page_view_anon_reqs }}
{{ render 'admin_report_counts' page_view_logged_in_reqs }}
{{ render 'admin_report_counts' page_view_crawler_reqs }}
{{ render 'admin_report_counts' page_view_total_reqs }}
{{/unless}}
</table>
</div>
<div class="dashboard-stats">
<table class="table table-condensed table-hover">
<thead>
<tr>
<th class="title" title="{{i18n 'admin.dashboard.private_messages_title'}}"><i class="fa fa-envelope"></i> {{i18n 'admin.dashboard.private_messages_short'}}</th>
<th>{{i18n 'admin.dashboard.reports.today'}}</th>
<th>{{i18n 'admin.dashboard.reports.yesterday'}}</th>
<th>{{i18n 'admin.dashboard.reports.last_7_days'}}</th>
<th>{{i18n 'admin.dashboard.reports.last_30_days'}}</th>
<th>{{i18n 'admin.dashboard.reports.all'}}</th>
</tr>
</thead>
{{#unless loading}}
@ -90,17 +112,54 @@
<thead>
<tr>
<th>&nbsp;</th>
<th>{{i18n admin.dashboard.reports.today}}</th>
<th>{{i18n admin.dashboard.reports.yesterday}}</th>
<th>{{i18n admin.dashboard.reports.7_days_ago}}</th>
<th>{{i18n admin.dashboard.reports.30_days_ago}}</th>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
{{#unless loading}}
{{ render 'admin/templates/reports/per_day_counts_report' visits tagName="tbody"}}
{{/unless}}
<tbody>
{{#unless loading}}
<tr>
<td>{{i18n 'admin.dashboard.uploads'}}</td>
<td>{{disk_space.uploads_used}} ({{i18n 'admin.dashboard.space_free' size=disk_space.uploads_free}})</td>
<td><a href="/admin/backups">{{i18n 'admin.dashboard.backups'}}</a></td>
<td>{{disk_space.backups_used}} ({{i18n 'admin.dashboard.space_free' size=disk_space.backups_free}})</td>
</tr>
{{/unless}}
</tbody>
</table>
</div>
{{#unless loading}}
{{#if showTrafficReport}}
<div class="dashboard-stats">
<table class="table table-condensed table-hover">
<thead>
<tr>
<th class="title" title="{{i18n 'admin.dashboard.traffic'}}">{{i18n 'admin.dashboard.traffic_short'}}</th>
<th>{{i18n 'admin.dashboard.reports.today'}}</th>
<th>{{i18n 'admin.dashboard.reports.yesterday'}}</th>
<th>{{i18n 'admin.dashboard.reports.last_7_days'}}</th>
<th>{{i18n 'admin.dashboard.reports.last_30_days'}}</th>
<th>{{i18n 'admin.dashboard.reports.all'}}</th>
</tr>
</thead>
{{#unless loading}}
{{ render 'admin_report_counts' http_2xx_reqs }}
{{ render 'admin_report_counts' http_3xx_reqs}}
{{ render 'admin_report_counts' http_4xx_reqs}}
{{ render 'admin_report_counts' http_5xx_reqs}}
{{ render 'admin_report_counts' http_background_reqs }}
{{ render 'admin_report_counts' http_total_reqs }}
{{/unless}}
</table>
</div>
{{else}}
<div class="dashboard-stats">
<a href {{action showTrafficReport}}>{{i18n 'admin.dashboard.show_traffic_report'}}</a>
</div>
{{/if}}
{{/unless}}
</div>
<div class="dashboard-right">
@ -110,7 +169,7 @@
<div class="look-here"><i class="fa fa-exclamation-triangle"></i></div>
<div class="problem-messages">
<p {{bind-attr class="loadingProblems:invisible"}}>
{{i18n admin.dashboard.problems_found}}
{{i18n 'admin.dashboard.problems_found'}}
<ul {{bind-attr class="loadingProblems:invisible"}}>
{{#each problem in problems}}
<li>{{{problem}}}</li>
@ -118,8 +177,8 @@
</ul>
</p>
<p class="actions">
<small>{{i18n admin.dashboard.last_checked}}: {{problemsTimestamp}}</small>
<button {{action "refreshProblems"}} class="btn btn-small"><i class="fa fa-refresh"></i>{{i18n admin.dashboard.refresh_problems}}</button>
<small>{{i18n 'admin.dashboard.last_checked'}}: {{problemsTimestamp}}</small>
{{d-button action="refreshProblems" class="btn-small" icon="refresh" label="admin.dashboard.refresh_problems"}}
</p>
</div>
<div class="clearfix"></div>
@ -130,8 +189,8 @@
<div class="look-here">&nbsp;</div>
<div class="problem-messages">
<p>
{{i18n admin.dashboard.no_problems}}
<button {{action "refreshProblems"}} class="btn btn-small"><i class="fa fa-refresh"></i>{{i18n admin.dashboard.refresh_problems}}</button>
{{i18n 'admin.dashboard.no_problems'}}
{{d-button action="refreshProblems" class="btn-small" icon="refresh" label="admin.dashboard.refresh_problems"}}
</p>
</div>
<div class="clearfix"></div>
@ -143,7 +202,7 @@
<table class="table table-condensed table-hover">
<thead>
<tr>
<th class="title">{{top_referred_topics.title}} ({{i18n admin.dashboard.reports.last_30_days}})</th>
<th class="title">{{top_referred_topics.title}} ({{i18n 'admin.dashboard.reports.last_30_days'}})</th>
<th>{{top_referred_topics.ytitles.num_clicks}}</th>
</tr>
</thead>
@ -170,18 +229,18 @@
<table class="table table-condensed table-hover">
<thead>
<tr>
<th class="title">{{top_traffic_sources.title}} ({{i18n admin.dashboard.reports.last_30_days}})</th>
<th class="title">{{top_traffic_sources.title}} ({{i18n 'admin.dashboard.reports.last_30_days'}})</th>
<th>{{top_traffic_sources.ytitles.num_clicks}}</th>
<th>{{top_traffic_sources.ytitles.num_topics}}</th>
</tr>
</thead>
{{#unless loading}}
{{#each top_traffic_sources.data}}
{{#each s in top_traffic_sources.data}}
<tbody>
<tr>
<td class="title">{{domain}}</td>
<td class="value">{{num_clicks}}</td>
<td class="value">{{num_topics}}</td>
<td class="title">{{s.domain}}</td>
<td class="value">{{s.num_clicks}}</td>
<td class="value">{{s.num_topics}}</td>
</tr>
</tbody>
{{/each}}
@ -193,18 +252,18 @@
<table class="table table-condensed table-hover">
<thead>
<tr>
<th class="title">{{top_referrers.title}} ({{i18n admin.dashboard.reports.last_30_days}})</th>
<th class="title">{{top_referrers.title}} ({{i18n 'admin.dashboard.reports.last_30_days'}})</th>
<th>{{top_referrers.ytitles.num_clicks}}</th>
<th>{{top_referrers.ytitles.num_topics}}</th>
</tr>
</thead>
{{#unless loading}}
{{#each top_referrers.data}}
{{#each r in top_referrers.data}}
<tbody>
<tr>
<td class="title">{{#link-to 'adminUser' this}}{{unbound username}}{{/link-to}}</td>
<td class="value">{{num_clicks}}</td>
<td class="value">{{num_topics}}</td>
<td class="title">{{#link-to 'adminUser' r}}{{unbound r.username}}{{/link-to}}</td>
<td class="value">{{r.num_clicks}}</td>
<td class="value">{{r.num_topics}}</td>
</tr>
</tbody>
{{/each}}
@ -215,7 +274,7 @@
<div class='clearfix'></div>
<div class="dashboard-stats pull-right">
<div class="pull-right">{{i18n admin.dashboard.last_updated}} {{updatedTimestamp}}</div>
<div class="pull-right">{{i18n 'admin.dashboard.last_updated'}} {{updatedTimestamp}}</div>
<div class='clearfix'></div>
</div>
<div class='clearfix'></div>

View File

@ -1,14 +1,10 @@
<div class='admin-controls'>
<div class='span15'>
<ul class="nav nav-pills">
<li>{{#link-to 'adminEmail.index'}}{{i18n admin.email.settings}}{{/link-to}}</li>
<li>{{#link-to 'adminEmail.all'}}{{i18n admin.email.all}}{{/link-to}}</li>
<li>{{#link-to 'adminEmail.sent'}}{{i18n admin.email.sent}}{{/link-to}}</li>
<li>{{#link-to 'adminEmail.skipped'}}{{i18n admin.email.skipped}}{{/link-to}}</li>
<li>{{#link-to 'adminEmail.previewDigest'}}{{i18n admin.email.preview_digest}}{{/link-to}}</li>
</ul>
</div>
</div>
{{#admin-nav}}
{{admin-nav-item route='adminEmail.index' label='admin.email.settings'}}
{{admin-nav-item route='adminEmail.all' label='admin.email.all'}}
{{admin-nav-item route='adminEmail.sent' label='admin.email.sent'}}
{{admin-nav-item route='adminEmail.skipped' label='admin.email.skipped'}}
{{admin-nav-item route='adminEmail.previewDigest' label='admin.email.preview_digest'}}
{{/admin-nav}}
<div class="admin-container">
{{outlet}}

View File

@ -1,39 +1,39 @@
<table class='table'>
<thead>
<tr>
<th>{{i18n admin.email.time}}</th>
<th>{{i18n admin.email.user}}</th>
<th>{{i18n admin.email.to_address}}</th>
<th>{{i18n admin.email.email_type}}</th>
<th>{{i18n admin.email.skipped_reason}}</th>
<th>{{i18n 'admin.email.time'}}</th>
<th>{{i18n 'admin.email.user'}}</th>
<th>{{i18n 'admin.email.to_address'}}</th>
<th>{{i18n 'admin.email.email_type'}}</th>
<th>{{i18n 'admin.email.skipped_reason'}}</th>
</tr>
</thead>
<tr class="filters">
<td>{{i18n admin.email.logs.filters.title}}</td>
<td>{{i18n 'admin.email.logs.filters.title'}}</td>
<td>{{text-field value=filter.user placeholderKey="admin.email.logs.filters.user_placeholder"}}</td>
<td>{{text-field value=filter.address placeholderKey="admin.email.logs.filters.address_placeholder"}}</td>
<td>{{text-field value=filter.type placeholderKey="admin.email.logs.filters.type_placeholder"}}</td>
<td>{{text-field value=filter.skipped_reason placeholderKey="admin.email.logs.filters.skipped_reason_placeholder"}}</td>
</tr>
{{#each model}}
{{#each l in model}}
<tr>
<td>{{format-date created_at}}</td>
<td>{{format-date l.created_at}}</td>
<td>
{{#if user}}
{{#link-to 'adminUser' user}}{{avatar user imageSize="tiny"}}{{/link-to}}
{{#link-to 'adminUser' user}}{{user.username}}{{/link-to}}
{{#if l.user}}
{{#link-to 'adminUser' l.user}}{{avatar l.user imageSize="tiny"}}{{/link-to}}
{{#link-to 'adminUser' l.user}}{{l.user.username}}{{/link-to}}
{{else}}
&mdash;
{{/if}}
</td>
<td><a href='mailto:{{unbound to_address}}'>{{to_address}}</a></td>
<td>{{email_type}}</td>
<td>{{skipped_reason}}</td>
<td><a href='mailto:{{unbound l.to_address}}'>{{l.to_address}}</a></td>
<td>{{l.email_type}}</td>
<td>{{l.skipped_reason}}</td>
</tr>
{{else}}
<tr><td colspan="5">{{i18n admin.email.logs.none}}</td></tr>
<tr><td colspan="5">{{i18n 'admin.email.logs.none'}}</td></tr>
{{/each}}
</table>

View File

@ -1,27 +1,27 @@
<table class="table">
<tr>
<th>{{i18n admin.email.delivery_method}}</th>
<td>{{model.delivery_method}}</td>
<th>{{i18n 'admin.email.delivery_method'}}</th>
<td>{{delivery_method}}</td>
</tr>
{{#each model.settings}}
{{#each s in model.settings}}
<tr>
<th style='width: 25%'>{{name}}</th>
<td>{{value}}</td>
<th style='width: 25%'>{{s.name}}</th>
<td>{{s.value}}</td>
</tr>
{{/each}}
</table>
<div class='admin-controls'>
{{#if sendingEmail}}
<div class='span15 controls'>{{i18n admin.email.sending_test}}</div>
<div class='span15 controls'>{{i18n 'admin.email.sending_test'}}</div>
{{else}}
<div class='controls'>
{{text-field value=testEmailAddress placeholderKey="admin.email.test_email_address"}}
</div>
<div class='span10 controls'>
<button class='btn btn-primary' {{action "sendTestEmail"}} {{bind-attr disabled="sendTestEmailDisabled"}}>{{i18n admin.email.send_test}}</button>
{{#if sentTestEmail}}<span class='result-message'>{{i18n admin.email.sent_test}}</span>{{/if}}
<button class='btn btn-primary' {{action "sendTestEmail"}} {{bind-attr disabled="sendTestEmailDisabled"}}>{{i18n 'admin.email.send_test'}}</button>
{{#if sentTestEmail}}<span class='result-message'>{{i18n 'admin.email.sent_test'}}</span>{{/if}}
</div>
{{/if}}
</div>

View File

@ -1,19 +1,19 @@
<p>{{i18n admin.email.preview_digest_desc}}</p>
<p>{{i18n 'admin.email.preview_digest_desc'}}</p>
<div class='admin-controls'>
<div class='span7 controls'>
<label for='last-seen'>{{i18n admin.email.last_seen_user}}</label>
<label for='last-seen'>{{i18n 'admin.email.last_seen_user'}}</label>
{{input type="date" value=lastSeen id="last-seen"}}
</div>
<div>
<button class='btn' {{action "refresh"}}>{{i18n admin.email.refresh}}</button>
<button class='btn' {{action "refresh"}}>{{i18n 'admin.email.refresh'}}</button>
</div>
<div class="span7 toggle">
<label>{{i18n admin.email.format}}</label>
<label>{{i18n 'admin.email.format'}}</label>
{{#if showHtml}}
<span>{{i18n admin.email.html}}</span> | <a href='#' {{action "toggleShowHtml"}}>{{i18n admin.email.text}}</a>
<span>{{i18n 'admin.email.html'}}</span> | <a href='#' {{action "toggleShowHtml"}}>{{i18n 'admin.email.text'}}</a>
{{else}}
<a href='#' {{action "toggleShowHtml"}}>{{i18n admin.email.html}}</a> | <span>{{i18n admin.email.text}}</span>
<a href='#' {{action "toggleShowHtml"}}>{{i18n 'admin.email.html'}}</a> | <span>{{i18n 'admin.email.text'}}</span>
{{/if}}
</div>
</div>

View File

@ -1,39 +1,39 @@
<table class='table'>
<thead>
<tr>
<th>{{i18n admin.email.sent_at}}</th>
<th>{{i18n admin.email.user}}</th>
<th>{{i18n admin.email.to_address}}</th>
<th>{{i18n admin.email.email_type}}</th>
<th>{{i18n admin.email.reply_key}}</th>
<th>{{i18n 'admin.email.sent_at'}}</th>
<th>{{i18n 'admin.email.user'}}</th>
<th>{{i18n 'admin.email.to_address'}}</th>
<th>{{i18n 'admin.email.email_type'}}</th>
<th>{{i18n 'admin.email.reply_key'}}</th>
</tr>
</thead>
<tr class="filters">
<td>{{i18n admin.email.logs.filters.title}}</td>
<td>{{i18n 'admin.email.logs.filters.title'}}</td>
<td>{{text-field value=filter.user placeholderKey="admin.email.logs.filters.user_placeholder"}}</td>
<td>{{text-field value=filter.address placeholderKey="admin.email.logs.filters.address_placeholder"}}</td>
<td>{{text-field value=filter.type placeholderKey="admin.email.logs.filters.type_placeholder"}}</td>
<td>{{text-field value=filter.reply_key placeholderKey="admin.email.logs.filters.reply_key_placeholder"}}</td>
</tr>
{{#each model}}
{{#each l in model}}
<tr>
<td>{{format-date created_at}}</td>
<td>{{format-date l.created_at}}</td>
<td>
{{#if user}}
{{#link-to 'adminUser' user}}{{avatar user imageSize="tiny"}}{{/link-to}}
{{#link-to 'adminUser' user}}{{user.username}}{{/link-to}}
{{#if l.user}}
{{#link-to 'adminUser' l.user}}{{avatar l.user imageSize="tiny"}}{{/link-to}}
{{#link-to 'adminUser' l.user}}{{l.user.username}}{{/link-to}}
{{else}}
&mdash;
{{/if}}
</td>
<td><a href='mailto:{{unbound to_address}}'>{{to_address}}</a></td>
<td>{{email_type}}</td>
<td>{{reply_key}}</td>
<td><a href='mailto:{{unbound l.to_address}}'>{{l.to_address}}</a></td>
<td>{{l.email_type}}</td>
<td>{{l.reply_key}}</td>
</tr>
{{else}}
<tr><td colspan="5">{{i18n admin.email.logs.none}}</td></tr>
<tr><td colspan="5">{{i18n 'admin.email.logs.none'}}</td></tr>
{{/each}}
</table>

View File

@ -1,39 +1,39 @@
<table class='table'>
<thead>
<tr>
<th>{{i18n admin.email.time}}</th>
<th>{{i18n admin.email.user}}</th>
<th>{{i18n admin.email.to_address}}</th>
<th>{{i18n admin.email.email_type}}</th>
<th>{{i18n admin.email.skipped_reason}}</th>
<th>{{i18n 'admin.email.time'}}</th>
<th>{{i18n 'admin.email.user'}}</th>
<th>{{i18n 'admin.email.to_address'}}</th>
<th>{{i18n 'admin.email.email_type'}}</th>
<th>{{i18n 'admin.email.skipped_reason'}}</th>
</tr>
</thead>
<tr class="filters">
<td>{{i18n admin.email.logs.filters.title}}</td>
<td>{{i18n 'admin.email.logs.filters.title'}}</td>
<td>{{text-field value=filter.user placeholderKey="admin.email.logs.filters.user_placeholder"}}</td>
<td>{{text-field value=filter.address placeholderKey="admin.email.logs.filters.address_placeholder"}}</td>
<td>{{text-field value=filter.type placeholderKey="admin.email.logs.filters.type_placeholder"}}</td>
<td>{{text-field value=filter.skipped_reason placeholderKey="admin.email.logs.filters.skipped_reason_placeholder"}}</td>
</tr>
{{#each model}}
{{#each l in model}}
<tr>
<td>{{format-date created_at}}</td>
<td>{{format-date l.created_at}}</td>
<td>
{{#if user}}
{{#link-to 'adminUser' user}}{{avatar user imageSize="tiny"}}{{/link-to}}
{{#link-to 'adminUser' user}}{{user.username}}{{/link-to}}
{{#if l.user}}
{{#link-to 'adminUser' l.user}}{{avatar l.user imageSize="tiny"}}{{/link-to}}
{{#link-to 'adminUser' l.user}}{{l.user.username}}{{/link-to}}
{{else}}
&mdash;
{{/if}}
</td>
<td><a href='mailto:{{unbound to_address}}'>{{to_address}}</a></td>
<td>{{email_type}}</td>
<td>{{skipped_reason}}</td>
<td><a href='mailto:{{unbound l.to_address}}'>{{l.to_address}}</a></td>
<td>{{l.email_type}}</td>
<td>{{l.skipped_reason}}</td>
</tr>
{{else}}
<tr><td colspan="5">{{i18n admin.email.logs.none}}</td></tr>
<tr><td colspan="5">{{i18n 'admin.email.logs.none'}}</td></tr>
{{/each}}
</table>

View File

@ -0,0 +1,30 @@
<div class='emoji'>
<h2>{{i18n 'admin.emoji.title'}}</h2>
<p class="desc">{{i18n 'admin.emoji.help'}}</p>
<p>{{emoji-uploader done="emojiUploaded"}}</p>
{{#if controller}}
<div class="span8">
<table id="custom_emoji">
<thead>
<tr>
<th>{{i18n "admin.emoji.image"}}</th>
<th>{{i18n "admin.emoji.name"}}</th>
<th></th>
</tr>
</thead>
<tbody>
{{#each e in controller}}
<tr>
<th><img class="emoji" src="{{unbound e.url}}" title="{{unbound e.name}}"></th>
<th>:{{e.name}}:</th>
<th><button {{action "destroy" e}} class='btn btn-danger no-text pull-right'>{{fa-icon 'trash-o'}} </button></th>
</tr>
{{/each}}
</tbody>
</table>
</div>
{{/if}}
</div>

View File

@ -4,8 +4,8 @@
<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>
<th class='flaggers'>{{i18n 'admin.flags.flagged_by'}}</th>
<th class='flaggers'>{{#if adminOldFlagsView}}{{i18n 'admin.flags.resolved_by'}}{{/if}}</th>
</tr>
</thead>
<tbody>
@ -16,12 +16,12 @@
{{#if flaggedPost.postAuthorFlagged}}
{{#if flaggedPost.user}}
{{#link-to 'adminUser' flaggedPost.user}}{{avatar flaggedPost.user imageSize="small"}}{{/link-to}}
{{#if flaggedPost.wasEdited}}<i class="fa fa-pencil" title="{{i18n admin.flags.was_edited}}"></i>{{/if}}
{{#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>
<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>
@ -29,7 +29,7 @@
<td class='excerpt'>
<h3>
{{#if flaggedPost.topic.isPrivateMessage}}
<span class="private-message-glyph">{{fa-icon envelope}}</span>
<span class="private-message-glyph">{{fa-icon "envelope"}}</span>
{{/if}}
{{topic-status topic=flaggedPost.topic}}
<a href='{{unbound flaggedPost.url}}'>{{flaggedPost.topic.title}}</a>
@ -42,20 +42,20 @@
<td class='flaggers'>
<table>
<tbody>
{{#each flaggedPost.flaggers}}
{{#each flagger in flaggedPost.flaggers}}
<tr>
<td class='avatar'>
{{#link-to 'adminUser' user}}
{{avatar user imageSize="small"}}
{{#link-to 'adminUser' flagger.user}}
{{avatar flagger.user imageSize="small"}}
{{/link-to}}
</td>
<td>
{{#link-to 'adminUser' user}}
{{user.username}}
{{#link-to 'adminUser' flagger.user}}
{{flagger.user.username}}
{{/link-to}}
{{format-age flaggedAt}}
{{format-age flagger.flaggedAt}}
<br />
{{flagType}}
{{flagger.flagType}}
</td>
</tr>
{{/each}}
@ -67,18 +67,18 @@
{{#if adminOldFlagsView}}
<table>
<tbody>
{{#each flaggedPost.flaggers}}
{{#each flagger in flaggedPost.flaggers}}
<tr>
<td class='avatar'>
{{#link-to 'adminUser' disposedBy}}
{{avatar disposedBy imageSize="small"}}
{{#link-to 'adminUser' flagger.disposedBy}}
{{avatar flagger.disposedBy imageSize="small"}}
{{/link-to}}
</td>
<td>
{{format-age disposedAt}}
{{{dispositionIcon}}}
{{#if tookAction}}
<i class='fa fa-gavel' title='{{i18n admin.flags.took_action}}'></i>
{{format-age flagger.disposedAt}}
{{{flagger.dispositionIcon}}}
{{#if flagger.tookAction}}
<i class='fa fa-gavel' title='{{i18n 'admin.flags.took_action'}}'></i>
{{/if}}
</td>
</tr>
@ -95,31 +95,31 @@
<td></td>
<td colspan="3">
<div>
{{{i18n admin.flags.topic_flagged}}}&nbsp;<a href='{{unbound flaggedPost.url}}' class="btn">{{i18n admin.flags.visit_topic}}</a>
{{{i18n 'admin.flags.topic_flagged'}}}&nbsp;<a href='{{unbound flaggedPost.url}}' class="btn">{{i18n 'admin.flags.visit_topic'}}</a>
</div>
</td>
</tr>
{{/if}}
{{#each flaggedPost.conversations}}
{{#each c in flaggedPost.conversations}}
<tr class='message'>
<td></td>
<td colspan="3">
<div>
{{#if response}}
{{#if c.response}}
<p>
{{#link-to 'adminUser' response.user}}{{avatar response.user imageSize="small"}}{{/link-to}}&nbsp;{{{response.excerpt}}}
{{#link-to 'adminUser' c.response.user}}{{avatar c.response.user imageSize="small"}}{{/link-to}}&nbsp;{{{c.response.excerpt}}}
</p>
{{#if reply}}
{{#if c.reply}}
<p>
{{#link-to 'adminUser' reply.user}}{{avatar reply.user imageSize="small"}}{{/link-to}}&nbsp;{{{reply.excerpt}}}
{{#if hasMore}}
<a href="{{unbound permalink}}">{{i18n admin.flags.more}}</a>
{{#link-to 'adminUser' c.reply.user}}{{avatar c.reply.user imageSize="small"}}{{/link-to}}&nbsp;{{{c.reply.excerpt}}}
{{#if c.hasMore}}
<a href="{{unbound c.permalink}}">{{i18n 'admin.flags.more'}}</a>
{{/if}}
</p>
{{/if}}
<a href="{{unbound permalink}}">
<button class='btn btn-reply'><i class="fa fa-reply"></i>&nbsp;{{i18n admin.flags.reply_message}}</button>
<a href="{{unbound c.permalink}}">
<button class='btn btn-reply'><i class="fa fa-reply"></i>&nbsp;{{i18n 'admin.flags.reply_message'}}</button>
</a>
{{/if}}
</div>
@ -130,14 +130,14 @@
<tr>
<td colspan="4" 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}}&hellip;</button>
<button title='{{i18n 'admin.flags.agree_title'}}' class='btn' {{action "showAgreeFlagModal" flaggedPost}}><i class="fa fa-thumbs-o-up"></i>{{i18n 'admin.flags.agree'}}&hellip;</button>
{{#if flaggedPost.postHidden}}
<button title='{{i18n admin.flags.disagree_flag_unhide_post_title}}' class='btn' {{action "disagreeFlags" flaggedPost}}><i class="fa fa-thumbs-o-down"></i>{{i18n admin.flags.disagree_flag_unhide_post}}</button>
<button title='{{i18n 'admin.flags.disagree_flag_unhide_post_title'}}' class='btn' {{action "disagreeFlags" flaggedPost}}><i class="fa fa-thumbs-o-down"></i>{{i18n 'admin.flags.disagree_flag_unhide_post'}}</button>
{{else}}
<button title='{{i18n admin.flags.disagree_flag_title}}' class='btn' {{action "disagreeFlags" flaggedPost}}><i class="fa fa-thumbs-o-down"></i>{{i18n admin.flags.disagree_flag}}</button>
<button title='{{i18n 'admin.flags.disagree_flag_title'}}' class='btn' {{action "disagreeFlags" flaggedPost}}><i class="fa fa-thumbs-o-down"></i>{{i18n 'admin.flags.disagree_flag'}}</button>
{{/if}}
<button title='{{i18n admin.flags.defer_flag_title}}' class='btn' {{action "deferFlags" flaggedPost}}><i class="fa fa-external-link"></i>{{i18n admin.flags.defer_flag}}</button>
<button title='{{i18n admin.flags.delete_title}}' class='btn btn-danger' {{action "showDeleteFlagModal" flaggedPost}}><i class="fa fa-trash-o"></i>{{i18n admin.flags.delete}}&hellip;</button>
<button title='{{i18n 'admin.flags.defer_flag_title'}}' class='btn' {{action "deferFlags" flaggedPost}}><i class="fa fa-external-link"></i>{{i18n 'admin.flags.defer_flag'}}</button>
<button title='{{i18n 'admin.flags.delete_title'}}' class='btn btn-danger' {{action "showDeleteFlagModal" flaggedPost}}><i class="fa fa-trash-o"></i>{{i18n 'admin.flags.delete'}}&hellip;</button>
{{/if}}
</td>
</tr>
@ -149,5 +149,5 @@
{{loading-spinner condition=view.loading}}
{{else}}
<p>{{i18n admin.flags.no_results}}</p>
<p>{{i18n 'admin.flags.no_results'}}</p>
{{/if}}

View File

@ -1,11 +1,7 @@
<div class='admin-controls'>
<div class='span15'>
<ul class="nav nav-pills">
<li>{{#link-to 'adminFlags.list' 'active'}}{{i18n admin.flags.active}}{{/link-to}}</li>
<li>{{#link-to 'adminFlags.list' 'old'}}{{i18n admin.flags.old}}{{/link-to}}</li>
</ul>
</div>
</div>
{{#admin-nav}}
{{admin-nav-item route='adminFlags.list' routeParam='active' label='admin.flags.active'}}
{{admin-nav-item route='adminFlags.list' routeParam='old' label='admin.flags.old'}}
{{/admin-nav}}
<div class="admin-container">
{{outlet}}

View File

@ -1,29 +1,64 @@
{{#if automatic}}
<h3>{{name}}</h3>
{{else}}
{{text-field value=name placeholderKey="admin.groups.name_placeholder"}}
{{/if}}
<form class="form-horizontal">
<div class="control-group">
<label class="control-label">{{i18n admin.groups.group_members}}</label>
<div class="controls">
{{user-selector usernames=usernames id="group-users" placeholderKey="admin.groups.selector_placeholder" tabindex="1" disabled=automatic}}
<div>
{{#if automatic}}
<h3>{{name}}</h3>
{{else}}
<label for="name">{{i18n 'admin.groups.name'}}</label>
{{text-field name="name" value=name placeholderKey="admin.groups.name_placeholder"}}
{{/if}}
</div>
</div>
<div class="control-group">
<div class="controls">
{{input type="checkbox" checked=visible}} {{i18n groups.visible}}
{{#if id}}
<div>
<label>{{i18n 'admin.groups.group_members'}} ({{user_count}})</label>
<div>
<a {{bind-attr class=":previous showingFirst:disabled"}} {{action "previous"}}>{{fa-icon "fast-backward"}}</a>
{{currentPage}}/{{totalPages}}
<a {{bind-attr class=":next showingLast:disabled"}} {{action "next"}}>{{fa-icon "fast-forward"}}</a>
</div>
<div class="ac-wrap clearfix">
{{each member in members itemView="group-member"}}
</div>
</div>
{{#unless automatic}}
<div>
<label for="user-selector">{{i18n 'admin.groups.add_members'}}</label>
{{user-selector usernames=usernames placeholderKey="admin.groups.selector_placeholder" id="user-selector"}}
<button {{action "addMembers"}} class='btn add'>{{fa-icon "plus"}} {{i18n 'admin.groups.add'}}</button>
</div>
{{/unless}}
{{/if}}
<div>
<label>
{{input type="checkbox" checked=visible}}
{{i18n 'groups.visible'}}
</label>
</div>
</div>
<div class="control-group">
<label class="control-label">{{i18n groups.alias_levels.title}}</label>
<div class="controls">
{{combo-box valueAttribute="value" value=alias_level content=aliasLevelOptions}}
<div>
<label for="alias">{{i18n 'groups.alias_levels.title'}}</label>
{{combo-box name="alias" valueAttribute="value" value=alias_level content=aliasLevelOptions}}
</div>
</div>
<div class='controls'>
<button {{action "save"}} {{bind-attr disabled="disableSave"}} class='btn'>{{i18n admin.customize.save}}</button>
{{#unless automatic}}
<button {{action "destroy"}} class='btn btn-danger'><i class="fa fa-trash-o"></i>{{i18n admin.customize.delete}}</button>
<div>
<label for="automatic_membership">{{i18n 'admin.groups.automatic_membership_email_domains'}}</label>
{{list-setting name="automatic_membership" settingValue=emailDomains}}
<label>
{{input type="checkbox" checked=automatic_membership_retroactive}}
{{i18n 'admin.groups.automatic_membership_retroactive'}}
</label>
</div>
{{/unless}}
</div>
<div class='buttons'>
<button {{action "save"}} {{bind-attr disabled="disableSave"}} class='btn btn-primary'>{{i18n 'admin.customize.save'}}</button>
{{#unless automatic}}
<button {{action "destroy"}} class='btn btn-danger'>{{fa-icon "trash-o"}}{{i18n 'admin.customize.delete'}}</button>
{{/unless}}
</div>
</form>

View File

@ -0,0 +1 @@
{{avatar member imageSize="small"}} {{member.username}} {{#unless automatic}}<a class='remove' {{action "removeMember" member}}>{{fa-icon "times"}}</a>{{/unless}}

View File

@ -1,20 +1,8 @@
<div class='row groups'>
<div class='content-list span6'>
<h3>{{i18n admin.groups.edit}}</h3>
<ul>
{{#each group in arrangedContent}}
<li>
<a href='#' {{action "showGroup" group}}>{{group.name}} <span class="count">{{group.userCountDisplay}}</span></a>
</li>
{{/each}}
</ul>
<div class='controls'>
<button class='btn' {{bind-attr disabled="refreshingAutoGroups"}} {{action "refreshAutoGroups"}}><i class="fa fa-refresh"></i>{{i18n admin.groups.refresh}}</button>
<button class='btn' {{action "newGroup"}}><i class="fa fa-plus"></i>{{i18n admin.groups.new}}</button>
</div>
</div>
{{#admin-nav}}
{{admin-nav-item route='adminGroupsType' routeParam='custom' label='admin.groups.custom'}}
{{admin-nav-item route='adminGroupsType' routeParam='automatic' label='admin.groups.automatic'}}
{{/admin-nav}}
<div class='content-editor'>
{{outlet}}
</div>
<div class="admin-container">
{{outlet}}
</div>

View File

@ -1 +0,0 @@
{{i18n admin.groups.about}}

View File

@ -0,0 +1,20 @@
<div class='row groups'>
<div class='content-list span6'>
<h3>{{i18n 'admin.groups.edit'}}</h3>
<ul>
{{#each group in controller}}
<li>
{{#link-to "adminGroup" group.type group.name}}{{group.name}} <span class="count">{{group.userCountDisplay}}</span>{{/link-to}}
</li>
{{/each}}
</ul>
<div class='controls'>
{{d-button action="newGroup" icon="plus" label="admin.groups.new"}}
{{d-button action="refreshAutoGroups" icon="refresh" label="admin.groups.refresh" disabled=refreshingAutoGroups}}
</div>
</div>
<div class='content-editor'>
{{outlet}}
</div>
</div>

View File

@ -1,14 +1,12 @@
<div class='admin-controls'>
<div class='span15'>
<ul class="nav nav-pills">
<li>{{#link-to 'adminLogs.staffActionLogs'}}{{i18n admin.logs.staff_actions.title}}{{/link-to}}</li>
<li>{{#link-to 'adminLogs.screenedEmails'}}{{i18n admin.logs.screened_emails.title}}{{/link-to}}</li>
<li>{{#link-to 'adminLogs.screenedIpAddresses'}}{{i18n admin.logs.screened_ips.title}}{{/link-to}}</li>
<li>{{#link-to 'adminLogs.screenedUrls'}}{{i18n admin.logs.screened_urls.title}}{{/link-to}}</li>
<li><a href="/logs" data-auto-route="true">{{i18n admin.logs.logster.title}}</a></li>
</ul>
</div>
</div>
{{#admin-nav}}
{{admin-nav-item route='adminLogs.staffActionLogs' label='admin.logs.staff_actions.title'}}
{{admin-nav-item route='adminLogs.screenedEmails' label='admin.logs.screened_emails.title'}}
{{admin-nav-item route='adminLogs.screenedIpAddresses' label='admin.logs.screened_ips.title'}}
{{admin-nav-item route='adminLogs.screenedUrls' label='admin.logs.screened_urls.title'}}
{{#if currentUser.admin}}
{{admin-nav-item href='/logs' label='admin.logs.logster.title'}}
{{/if}}
{{/admin-nav}}
<div class="admin-container">
{{outlet}}

View File

@ -1,22 +1,19 @@
<section class="field">
<b>{{i18n admin.customize.css}}</b>:
<b>{{i18n 'admin.customize.css'}}</b>:
{{#if stylesheet}}
({{i18n character_count count=stylesheet.length}})
({{i18n 'character_count' count=stylesheet.length}})
{{/if}}
<br/>
{{textarea value=stylesheet class="plain"}}
</section>
<section class="field">
<b>{{i18n admin.customize.header}}</b>:
<b>{{i18n 'admin.customize.header'}}</b>:
{{#if header}}
({{i18n character_count count=header.length}})
({{i18n 'character_count' count=header.length}})
{{/if}}
<br/>
{{textarea value=header class="plain"}}
</section>
<section class="field">
<b>{{i18n admin.customize.enabled}}</b>: {{enabled}}
</section>
<section class="field">
<b>{{i18n admin.customize.override_default}}</b>: {{override_default_style}}
<b>{{i18n 'admin.customize.enabled'}}</b>: {{enabled}}
</section>

View File

@ -2,5 +2,5 @@
<pre>{{details}}</pre>
</div>
<div class="modal-footer">
<button class='btn btn-primary' {{action "closeModal"}}>{{i18n close}}</button>
<button class='btn btn-primary' {{action "closeModal"}}>{{i18n 'close'}}</button>
</div>

View File

@ -1,16 +1,20 @@
<p>{{i18n admin.logs.screened_emails.description}}</p>
<p>
{{i18n 'admin.logs.screened_emails.description'}}
<button class="btn pull-right" {{action "exportScreenedEmailList"}} title="{{i18n 'admin.export_csv.button_title.screened_email'}}">{{fa-icon "download"}}{{i18n 'admin.export_csv.button_text'}}</button>
</p>
</br>
{{#loading-spinner condition=loading}}
{{#if model.length}}
<div class='table screened-emails'>
<div class="heading-container">
<div class="col heading first email">{{i18n admin.logs.screened_emails.email}}</div>
<div class="col heading action">{{i18n admin.logs.action}}</div>
<div class="col heading match_count">{{i18n admin.logs.match_count}}</div>
<div class="col heading last_match_at">{{i18n admin.logs.last_match_at}}</div>
<div class="col heading created_at">{{i18n admin.logs.created_at}}</div>
<div class="col heading ip_address">{{i18n admin.logs.ip_address}}</div>
<div class="col heading first email">{{i18n 'admin.logs.screened_emails.email'}}</div>
<div class="col heading action">{{i18n 'admin.logs.action'}}</div>
<div class="col heading match_count">{{i18n 'admin.logs.match_count'}}</div>
<div class="col heading last_match_at">{{i18n 'admin.logs.last_match_at'}}</div>
<div class="col heading created_at">{{i18n 'admin.logs.created_at'}}</div>
<div class="col heading ip_address">{{i18n 'admin.logs.ip_address'}}</div>
<div class="col heading action"></div>
<div class="clearfix"></div>
</div>
@ -19,6 +23,6 @@
</div>
{{else}}
{{i18n search.no_results}}
{{i18n 'search.no_results'}}
{{/if}}
{{/loading-spinner}}

View File

@ -6,5 +6,5 @@
<div class="col last_match_at">{{age-with-tooltip last_match_at}}</div>
<div class="col created_at">{{age-with-tooltip created_at}}</div>
<div class="col ip_address">{{ip_address}}</div>
<div class="col action"><button class="btn" {{action "clearBlock" this}}><i class='fa fa-check'></i> {{i18n admin.logs.screened_emails.actions.allow}}</button></div>
<div class="col action"><button class="btn" {{action "clearBlock" this}}><i class='fa fa-check'></i> {{i18n 'admin.logs.screened_emails.actions.allow'}}</button></div>
<div class="clearfix"></div>

View File

@ -1,5 +1,9 @@
<p>{{i18n admin.logs.screened_ips.description}}</p>
<p>{{i18n 'admin.logs.screened_ips.description'}}</p>
<div class="pull-right">
{{text-field value=filter class="ip-address-input" placeholderKey="admin.logs.screened_ips.form.filter" autocorrect="off" autocapitalize="off"}}
<button class="btn" {{action "rollUp"}} title="{{i18n 'admin.logs.screened_ips.roll_up.title'}}">{{i18n 'admin.logs.screened_ips.roll_up.text'}}</button>
<button class="btn" {{action "exportScreenedIpList"}} title="{{i18n 'admin.export_csv.button_title.screened_ip'}}">{{fa-icon "download"}}{{i18n 'admin.export_csv.button_text'}}</button>
</div>
{{screened-ip-address-form action="recordAdded"}}
<br/>
@ -8,11 +12,11 @@
<div class='table admin-logs-table screened-ip-addresses'>
<div class="heading-container">
<div class="col heading first ip_address">{{i18n admin.logs.ip_address}}</div>
<div class="col heading action">{{i18n admin.logs.action}}</div>
<div class="col heading match_count">{{i18n admin.logs.match_count}}</div>
<div class="col heading last_match_at">{{i18n admin.logs.last_match_at}}</div>
<div class="col heading created_at">{{i18n admin.logs.created_at}}</div>
<div class="col heading first ip_address">{{i18n 'admin.logs.ip_address'}}</div>
<div class="col heading action">{{i18n 'admin.logs.action'}}</div>
<div class="col heading match_count">{{i18n 'admin.logs.match_count'}}</div>
<div class="col heading last_match_at">{{i18n 'admin.logs.last_match_at'}}</div>
<div class="col heading created_at">{{i18n 'admin.logs.created_at'}}</div>
<div class="col heading actions"></div>
<div class="clearfix"></div>
</div>
@ -21,6 +25,6 @@
</div>
{{else}}
{{i18n search.no_results}}
{{i18n 'search.no_results'}}
{{/if}}
{{/loading-spinner}}

View File

@ -2,7 +2,13 @@
{{#if editing}}
{{text-field value=ip_address autofocus="autofocus"}}
{{else}}
<span {{action "edit" this}}>{{ip_address}}</span>
<span {{action "edit" this}}>
{{#if isRange}}
<strong>{{ip_address}}</strong>
{{else}}
{{ip_address}}
{{/if}}
</span>
{{/if}}
</div>
<div class="col action">
@ -21,13 +27,13 @@
<button class="btn btn-danger" {{action "destroy" this}}><i class="fa fa-trash-o"></i></button>
<button class="btn" {{action "edit" this}}><i class="fa fa-pencil"></i></button>
{{#if isBlocked}}
<button class="btn" {{action "allow" this}}><i {{bind-attr class=":fa doNothingIcon"}}></i> {{i18n admin.logs.screened_ips.actions.do_nothing}}</button>
<button class="btn" {{action "allow" this}}><i {{bind-attr class=":fa doNothingIcon"}}></i> {{i18n 'admin.logs.screened_ips.actions.do_nothing'}}</button>
{{else}}
<button class="btn" {{action "block" this}}><i {{bind-attr class=":fa blockIcon"}}></i> {{i18n admin.logs.screened_ips.actions.block}}</button>
<button class="btn" {{action "block" this}}><i {{bind-attr class=":fa blockIcon"}}></i> {{i18n 'admin.logs.screened_ips.actions.block'}}</button>
{{/if}}
{{else}}
<button class="btn" {{action "save" this}}>{{i18n admin.logs.save}}</button>
<a {{action "cancel" this}}>{{i18n cancel}}</a>
<button class="btn" {{action "save" this}}>{{i18n 'admin.logs.save'}}</button>
<a {{action "cancel" this}}>{{i18n 'cancel'}}</a>
{{/unless}}
</div>
<div class="clearfix"></div>

View File

@ -1,20 +1,24 @@
<p>{{i18n admin.logs.screened_urls.description}}</p>
<p>
{{i18n 'admin.logs.screened_urls.description'}}
<button class="btn pull-right" {{action "exportScreenedUrlList"}} title="{{i18n 'admin.export_csv.button_title.screened_url'}}">{{fa-icon "download"}}{{i18n 'admin.export_csv.button_text'}}</button>
</p>
</br>
{{#loading-spinner condition=loading}}
{{#if model.length}}
<div class='table screened-urls'>
<div class="heading-container">
<div class="col heading first domain">{{i18n admin.logs.screened_urls.domain}}</div>
<div class="col heading action">{{i18n admin.logs.action}}</div>
<div class="col heading match_count">{{i18n admin.logs.match_count}}</div>
<div class="col heading last_match_at">{{i18n admin.logs.last_match_at}}</div>
<div class="col heading created_at">{{i18n admin.logs.created_at}}</div>
<div class="col heading first domain">{{i18n 'admin.logs.screened_urls.domain'}}</div>
<div class="col heading action">{{i18n 'admin.logs.action'}}</div>
<div class="col heading match_count">{{i18n 'admin.logs.match_count'}}</div>
<div class="col heading last_match_at">{{i18n 'admin.logs.last_match_at'}}</div>
<div class="col heading created_at">{{i18n 'admin.logs.created_at'}}</div>
<div class="clearfix"></div>
</div>
{{view 'screened-urls-list' content=controller}}
</div>
{{else}}
{{i18n search.no_results}}
{{i18n 'search.no_results'}}
{{/if}}
{{/loading-spinner}}

View File

@ -1,10 +1,10 @@
<div>
<ul class="nav nav-pills">
<li {{bind-attr class="newSelected:active"}}>
<a href="#" {{action "selectNew"}}>{{i18n admin.logs.staff_actions.new_value}}</a>
<a href="#" {{action "selectNew"}}>{{i18n 'admin.logs.staff_actions.new_value'}}</a>
</li>
<li {{bind-attr class="previousSelected:active"}}>
<a href="#" {{action "selectPrevious"}}>{{i18n admin.logs.staff_actions.previous_value}}</a>
<a href="#" {{action "selectPrevious"}}>{{i18n 'admin.logs.staff_actions.previous_value'}}</a>
</li>
</ul>
<div class="modal-body">
@ -14,7 +14,7 @@
{{partial "admin/templates/logs/site_customization_change_details"}}
{{/with}}
{{else}}
{{i18n admin.logs.staff_actions.deleted}}
{{i18n 'admin.logs.staff_actions.deleted'}}
{{/if}}
</div>
<div {{bind-attr class=":modal-tab :previous-tab previousSelected::invisible"}}>
@ -23,11 +23,11 @@
{{partial "admin/templates/logs/site_customization_change_details"}}
{{/with}}
{{else}}
{{i18n admin.logs.staff_actions.no_previous}}
{{i18n 'admin.logs.staff_actions.no_previous'}}
{{/if}}
</div>
</div>
<div class="modal-footer">
<button class='btn btn-primary' {{action "closeModal"}}>{{i18n close}}</button>
<button class='btn btn-primary' {{action "closeModal"}}>{{i18n 'close'}}</button>
</div>
</div>

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