Version bump to v1.1.1

This commit is contained in:
Neil Lalonde 2014-11-18 15:57:50 -05:00
commit 6e0152ab94
289 changed files with 54501 additions and 48652 deletions

View File

@ -7,7 +7,7 @@ root = true
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = spaces
indent_style = space
indent_size = 2
[*.md]

View File

@ -1,16 +1,40 @@
language: ruby
env:
global:
- DISCOURSE_HOSTNAME=www.example.com
- RUBY_GC_MALLOC_LIMIT=50000000
matrix:
- "RAILS_MASTER=1"
- "RAILS_MASTER=0"
matrix:
allow_failures:
- rvm: 2.0.0
env: "RAILS_MASTER=1"
- rvm: 2.1
env: "RAILS_MASTER=1"
fast_finish: true
rvm:
- 2.0.0
- 2.1.2
- 2.1
services:
- redis-server
sudo: false
cache: bundler
before_install:
- npm i -g jshint
- jshint .
before_script:
- psql -c 'create database discourse_test;' -U postgres
- export DISCOURSE_HOSTNAME=www.example.com
- export RUBY_GC_MALLOC_LIMIT=50000000
- bundle exec rake db:migrate
bundler_args: --without development
bundler_args: --without development --deployment --retry=3 --jobs=3
script: 'bundle exec rspec && bundle exec rake plugin:spec && bundle exec rake qunit:test'
services:
- redis-server

15
Gemfile
View File

@ -4,7 +4,7 @@ source 'https://rubygems.org'
module ::Kernel
def rails_master?
ENV["RAILS_MASTER"]
ENV["RAILS_MASTER"] == '1'
end
end
@ -70,12 +70,13 @@ 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 'actionpack-action_caching', git: 'https://github.com/rails/actionpack-action_caching.git'
else
gem 'rails'
gem 'actionpack-action_caching'
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`.
@ -91,7 +92,11 @@ gem 'redis', require: ["redis", "redis/connection/hiredis"]
# We use some ams 0.8.0 features, need to amend code
# to support 0.9 etc, bench needs to run and ensure no
# perf regressions
gem 'active_model_serializers', '~> 0.8.0'
if rails_master?
gem 'active_model_serializers', github: 'rails-api/active_model_serializers', branch: '0-8-stable'
else
gem 'active_model_serializers', '~> 0.8.0'
end
gem 'onebox'
@ -241,6 +246,8 @@ gem 'memory_profiler', require: false, platform: :mri_21
gem 'rmmseg-cpp', require: false
gem 'stringex', require: false
gem 'logster'
# perftools only works on 1.9 atm

View File

@ -6,31 +6,31 @@ PATH
GEM
remote: https://rubygems.org/
specs:
actionmailer (4.1.7)
actionpack (= 4.1.7)
actionview (= 4.1.7)
actionmailer (4.1.8)
actionpack (= 4.1.8)
actionview (= 4.1.8)
mail (~> 2.5, >= 2.5.4)
actionpack (4.1.7)
actionview (= 4.1.7)
activesupport (= 4.1.7)
actionpack (4.1.8)
actionview (= 4.1.8)
activesupport (= 4.1.8)
rack (~> 1.5.2)
rack-test (~> 0.6.2)
actionpack-action_caching (1.1.1)
actionpack (>= 4.0.0, < 5.0)
actionview (4.1.7)
activesupport (= 4.1.7)
actionview (4.1.8)
activesupport (= 4.1.8)
builder (~> 3.1)
erubis (~> 2.7.0)
active_model_serializers (0.8.2)
activemodel (>= 3.0)
activemodel (4.1.7)
activesupport (= 4.1.7)
activemodel (4.1.8)
activesupport (= 4.1.8)
builder (~> 3.1)
activerecord (4.1.7)
activemodel (= 4.1.7)
activesupport (= 4.1.7)
activerecord (4.1.8)
activemodel (= 4.1.8)
activesupport (= 4.1.8)
arel (~> 5.0.0)
activesupport (4.1.7)
activesupport (4.1.8)
i18n (~> 0.6, >= 0.6.9)
json (~> 1.7, >= 1.7.7)
minitest (~> 5.1)
@ -99,7 +99,7 @@ GEM
fastimage (1.6.3)
addressable (~> 2.3, >= 2.3.5)
ffi (1.9.5)
flamegraph (0.0.5)
flamegraph (0.0.8)
fast_stack
fog (1.22.1)
fog-brightbox
@ -170,7 +170,7 @@ GEM
metaclass (0.0.4)
method_source (0.8.2)
mime-types (1.25.1)
mini_portile (0.6.0)
mini_portile (0.6.1)
minitest (5.4.2)
mocha (1.1.0)
metaclass (~> 0.0.1)
@ -185,8 +185,8 @@ GEM
net-ssh (>= 2.6.5)
net-ssh (2.9.1)
netrc (0.7.7)
nokogiri (1.6.3.1)
mini_portile (= 0.6.0)
nokogiri (1.6.4.1)
mini_portile (~> 0.6.0)
nokogumbo (1.1.12)
nokogiri
oauth (0.4.7)
@ -222,7 +222,7 @@ GEM
omniauth-twitter (1.0.1)
multi_json (~> 1.3)
omniauth-oauth (~> 1.0)
onebox (1.5.3)
onebox (1.5.5)
moneta (~> 0.7)
multi_json (~> 1.7)
mustache (~> 0.99)
@ -255,21 +255,21 @@ GEM
rack
rack-test (0.6.2)
rack (>= 1.0)
rails (4.1.7)
actionmailer (= 4.1.7)
actionpack (= 4.1.7)
actionview (= 4.1.7)
activemodel (= 4.1.7)
activerecord (= 4.1.7)
activesupport (= 4.1.7)
rails (4.1.8)
actionmailer (= 4.1.8)
actionpack (= 4.1.8)
actionview (= 4.1.8)
activemodel (= 4.1.8)
activerecord (= 4.1.8)
activesupport (= 4.1.8)
bundler (>= 1.3.0, < 2.0)
railties (= 4.1.7)
railties (= 4.1.8)
sprockets-rails (~> 2.0)
rails-observers (0.1.2)
activemodel (~> 4.0)
railties (4.1.7)
actionpack (= 4.1.7)
activesupport (= 4.1.7)
railties (4.1.8)
actionpack (= 4.1.8)
activesupport (= 4.1.8)
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
raindrops (0.13.0)
@ -371,6 +371,7 @@ GEM
activesupport (>= 3.0)
sprockets (~> 2.8)
stackprof (0.2.7)
stringex (2.5.2)
therubyracer (0.12.1)
libv8 (~> 3.16.14.0)
ref
@ -488,6 +489,7 @@ DEPENDENCIES
sinatra
spork-rails
stackprof
stringex
therubyracer
thin
timecop

View File

@ -1,73 +1,80 @@
GIT
remote: https://github.com/rails/actionpack-action_caching.git
revision: a9f3f09477b12b51faa6756005eee4103f7f4030
remote: git://github.com/rails-api/active_model_serializers.git
revision: b6b01d0b7396f3deaa6e661cedf4bc5efe2f4525
branch: 0-8-stable
specs:
actionpack-action_caching (1.1.0)
actionpack (>= 4.0.0, < 5.0)
active_model_serializers (0.8.2)
activemodel (>= 3.0)
GIT
remote: https://github.com/rails/arel.git
revision: 590c784a30b13153667f8db7915998d7731e24e5
specs:
arel (6.0.0.beta2)
GIT
remote: https://github.com/rails/rails.git
revision: 316962d0922992fbe756521bd7c94a751aa1253e
revision: eb26f24bde62cbbcd8ef0e7ee9c64060b098baff
specs:
actionmailer (4.2.0.beta1)
actionpack (= 4.2.0.beta1)
actionview (= 4.2.0.beta1)
activejob (= 4.2.0.beta1)
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.2)
actionpack (4.2.0.beta1)
actionview (= 4.2.0.beta1)
activesupport (= 4.2.0.beta1)
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.2)
rails-html-sanitizer (~> 1.0)
actionview (4.2.0.beta1)
activesupport (= 4.2.0.beta1)
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.2)
rails-html-sanitizer (~> 1.0)
activejob (4.2.0.beta1)
activesupport (= 4.2.0.beta1)
globalid (>= 0.2.3)
activemodel (4.2.0.beta1)
activesupport (= 4.2.0.beta1)
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.beta1)
activemodel (= 4.2.0.beta1)
activesupport (= 4.2.0.beta1)
arel (>= 6.0.0.beta1, < 6.1)
activesupport (4.2.0.beta1)
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.beta1)
actionmailer (= 4.2.0.beta1)
actionpack (= 4.2.0.beta1)
actionview (= 4.2.0.beta1)
activejob (= 4.2.0.beta1)
activemodel (= 4.2.0.beta1)
activerecord (= 4.2.0.beta1)
activesupport (= 4.2.0.beta1)
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.beta1)
railties (= 4.2.0.beta4)
sprockets-rails (~> 3.0.0.beta1)
railties (4.2.0.beta1)
actionpack (= 4.2.0.beta1)
activesupport (= 4.2.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)
GIT
remote: https://github.com/rails/sass-rails.git
revision: 423178729e7b84addcee78b33891644b1fc1705b
revision: b4b5f32a2928ef203f4b442bc538a572645de8e3
specs:
sass-rails (5.0.0.beta1)
railties (>= 4.0.0, < 5.0)
sass (~> 3.2)
sass (~> 3.2, >= 3.2.2)
sprockets (~> 2.12)
sprockets-rails (>= 2.0, < 4.0)
@ -79,16 +86,12 @@ PATH
GEM
remote: https://rubygems.org/
specs:
active_model_serializers (0.8.2)
activemodel (>= 3.0)
actionpack-action_caching (1.1.1)
actionpack (>= 4.0.0, < 5.0)
addressable (2.3.6)
airbrake (3.1.2)
activesupport
builder
annotate (2.6.5)
activerecord (>= 2.3.0)
rake (>= 0.8.7)
arel (6.0.0.beta1)
barber (0.4.2)
ember-source
execjs
@ -114,25 +117,25 @@ GEM
dotenv-deployment (~> 0.0.2)
dotenv-deployment (0.0.2)
email_reply_parser-discourse (0.6)
ember-data-source (1.0.0.beta.9)
ember-data-source (0.14)
ember-source
ember-rails (0.15.0)
ember-rails (0.14.1)
active_model_serializers
barber (>= 0.4.1)
ember-data-source (>= 1.0.0.beta.5)
ember-source (>= 1.1.0)
ember-data-source
ember-source
execjs (>= 1.2)
handlebars-source (> 1.0.0)
handlebars-source
jquery-rails (>= 1.0.17)
railties (>= 3.1)
ember-source (1.6.0.beta.2)
handlebars-source (~> 1.0)
erubis (2.7.0)
eventmachine (1.0.3)
excon (0.39.5)
excon (0.39.6)
execjs (2.2.1)
exifr (1.1.3)
fabrication (2.11.3)
fabrication (2.9.8)
fakeweb (1.3.0)
faraday (0.9.0)
multipart-post (>= 1.2, < 3)
@ -146,8 +149,8 @@ GEM
fast_xs (0.8.0)
fastimage (1.6.3)
addressable (~> 2.3, >= 2.3.5)
ffi (1.9.3)
flamegraph (0.0.5)
ffi (1.9.5)
flamegraph (0.0.8)
fast_stack
fog (1.22.1)
fog-brightbox
@ -155,7 +158,7 @@ GEM
fog-json
ipaddress (~> 0.5)
nokogiri (~> 1.5, >= 1.5.11)
fog-brightbox (0.5.0)
fog-brightbox (0.5.1)
fog-core (~> 1.22)
fog-json
inflecto
@ -176,7 +179,7 @@ GEM
gctools (0.2.3)
given_core (3.5.4)
sorcerer (>= 0.3.7)
globalid (0.2.3)
globalid (0.3.0)
activesupport (>= 4.1.0)
guess_html_encoding (0.0.9)
handlebars-source (1.3.0)
@ -205,7 +208,7 @@ GEM
librarian (0.1.2)
highline
thor (~> 0.15)
libv8 (3.16.14.3)
libv8 (3.16.14.7)
listen (0.7.3)
logster (0.1.6)
loofah (2.0.1)
@ -215,15 +218,15 @@ GEM
mime-types (~> 1.16)
treetop (~> 1.4.8)
memory_profiler (0.0.4)
message_bus (0.9.5)
message_bus (1.0.5)
eventmachine
rack (>= 1.1.3)
redis
metaclass (0.0.4)
method_source (0.8.2)
mime-types (1.25.1)
mini_portile (0.6.0)
minitest (5.4.1)
mini_portile (0.6.1)
minitest (5.4.3)
mocha (1.1.0)
metaclass (~> 0.0.1)
mock_redis (0.13.2)
@ -237,8 +240,8 @@ GEM
net-ssh (>= 2.6.5)
net-ssh (2.9.1)
netrc (0.7.7)
nokogiri (1.6.3.1)
mini_portile (= 0.6.0)
nokogiri (1.6.4.1)
mini_portile (~> 0.6.0)
nokogumbo (1.1.12)
nokogiri
oauth (0.4.7)
@ -274,7 +277,7 @@ GEM
omniauth-twitter (1.0.1)
multi_json (~> 1.3)
omniauth-oauth (~> 1.0)
onebox (1.4.9)
onebox (1.5.3)
moneta (~> 0.7)
multi_json (~> 1.7)
mustache (~> 0.99)
@ -293,7 +296,7 @@ GEM
pry (>= 0.9.10, < 0.11.0)
pry-rails (0.3.2)
pry (>= 0.9.10)
puma (2.9.0)
puma (2.9.1)
rack (>= 1.1, < 2.0)
qunit-rails (0.0.7)
railties
@ -307,13 +310,13 @@ GEM
rack
rack-test (0.6.2)
rack (>= 1.0)
rails-deprecated_sanitizer (1.0.2)
rails-deprecated_sanitizer (1.0.3)
activesupport (>= 4.2.0.alpha)
rails-dom-testing (1.0.2)
activesupport
rails-dom-testing (1.0.4)
activesupport (>= 4.2.0.beta, < 5.0)
nokogiri (~> 1.6.0)
rails-deprecated_sanitizer (>= 1.0.1)
rails-html-sanitizer (1.0.0)
rails-html-sanitizer (1.0.1)
loofah (~> 2.0)
rails-observers (0.1.2)
activemodel (~> 4.0)
@ -338,29 +341,28 @@ GEM
netrc (~> 0.7)
rinku (1.7.3)
rmmseg-cpp (0.2.9)
rspec (3.0.0)
rspec-core (~> 3.0.0)
rspec-expectations (~> 3.0.0)
rspec-mocks (~> 3.0.0)
rspec-core (3.0.4)
rspec-support (~> 3.0.0)
rspec-expectations (3.0.4)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.0.0)
rspec (2.99.0)
rspec-core (~> 2.99.0)
rspec-expectations (~> 2.99.0)
rspec-mocks (~> 2.99.0)
rspec-collection_matchers (1.0.0)
rspec-expectations (>= 2.99.0.beta1)
rspec-core (2.99.2)
rspec-expectations (2.99.2)
diff-lcs (>= 1.1.3, < 2.0)
rspec-given (3.5.4)
given_core (= 3.5.4)
rspec (>= 2.12)
rspec-mocks (3.0.4)
rspec-support (~> 3.0.0)
rspec-rails (3.0.2)
rspec-mocks (2.99.2)
rspec-rails (2.99.0)
actionpack (>= 3.0)
activemodel (>= 3.0)
activesupport (>= 3.0)
railties (>= 3.0)
rspec-core (~> 3.0.0)
rspec-expectations (~> 3.0.0)
rspec-mocks (~> 3.0.0)
rspec-support (~> 3.0.0)
rspec-support (3.0.4)
rspec-collection_matchers
rspec-core (~> 2.99.0)
rspec-expectations (~> 2.99.0)
rspec-mocks (~> 2.99.0)
rtlit (0.0.5)
ruby-openid (2.5.0)
ruby-readability (0.7.0)
@ -370,7 +372,7 @@ GEM
crass (~> 0.2.0)
nokogiri (>= 1.4.4)
nokogumbo (= 1.1.12)
sass (3.4.2)
sass (3.2.19)
seed-fu (2.3.3)
activerecord (>= 3.1, < 4.2)
activesupport (>= 3.1, < 4.2)
@ -380,16 +382,16 @@ GEM
shoulda-context (1.2.1)
shoulda-matchers (2.7.0)
activesupport (>= 3.0.0)
sidekiq (3.2.3)
celluloid (>= 0.15.2)
sidekiq (3.2.5)
celluloid (= 0.15.2)
connection_pool (>= 2.0.0)
json
redis (>= 3.0.6)
redis-namespace (>= 1.3.1)
simple-rss (1.3.1)
simplecov (0.9.0)
simplecov (0.9.1)
docile (~> 1.1.0)
multi_json
multi_json (~> 1.0)
simplecov-html (~> 0.8.0)
simplecov-html (0.8.0)
sinatra (1.4.5)
@ -402,7 +404,7 @@ GEM
spork-rails (4.0.0)
rails (>= 3.0.0, < 5)
spork (>= 1.0rc0)
sprockets (2.12.1)
sprockets (2.12.3)
hike (~> 1.2)
multi_json (~> 1.0)
rack (~> 1.0)
@ -412,6 +414,7 @@ GEM
activesupport (>= 4.0)
sprockets (~> 2.8)
stackprof (0.2.7)
stringex (2.5.2)
therubyracer (0.12.1)
libv8 (~> 3.16.14.0)
ref
@ -445,10 +448,10 @@ PLATFORMS
ruby
DEPENDENCIES
actionpack-action_caching!
active_model_serializers (~> 0.8.0)
airbrake (= 3.1.2)
actionpack-action_caching
active_model_serializers!
annotate
arel!
barber
better_errors
binding_of_caller
@ -457,7 +460,7 @@ DEPENDENCIES
ember-rails
ember-source (= 1.6.0.beta.2)
eventmachine
fabrication
fabrication (= 2.9.8)
fakeweb (~> 1.3.0)
fast_blank
fast_xor
@ -514,6 +517,7 @@ DEPENDENCIES
rest-client
rinku
rmmseg-cpp
rspec (= 2.99.0)
rspec-given
rspec-rails
rtlit
@ -529,6 +533,7 @@ DEPENDENCIES
sinatra
spork-rails
stackprof
stringex
therubyracer
thin
timecop

View File

@ -23,14 +23,14 @@ export default Ember.Component.extend({
}
if (!this.get("other_accounts")) {
this.set("other_accounts_loading", true);
this.set("otherAccountsLoading", true);
Discourse.AdminUser.findAll("active", {
"ip": this.get("ip"),
"exclude": this.get("user_id")
}).then(function (users) {
self.setProperties({
other_accounts: users,
other_accounts_loading: false,
otherAccountsLoading: false,
});
});
}

View File

@ -2,10 +2,10 @@ import ModalFunctionality from 'discourse/mixins/modal-functionality';
import ObjectController from 'discourse/controllers/object';
export default ObjectController.extend(ModalFunctionality, {
needs: ["adminFlags"],
needs: ["admin-flags-list"],
_agreeFlag: function (actionOnPost) {
var adminFlagController = this.get("controllers.adminFlags");
var adminFlagController = this.get("controllers.admin-flags-list");
var post = this.get("content");
var self = this;

View File

@ -3,13 +3,12 @@ import ModalFunctionality from 'discourse/mixins/modal-functionality';
import ObjectController from 'discourse/controllers/object';
export default ObjectController.extend(ModalFunctionality, {
needs: ["adminFlags"],
needs: ["admin-flags-list"],
actions: {
deletePostDeferFlag: function () {
var adminFlagController = this.get("controllers.adminFlags");
var adminFlagController = this.get("controllers.admin-flags-list");
var post = this.get("content");
var self = this;
@ -22,7 +21,7 @@ export default ObjectController.extend(ModalFunctionality, {
},
deletePostAgreeFlag: function () {
var adminFlagController = this.get("controllers.adminFlags");
var adminFlagController = this.get("controllers.admin-flags-list");
var post = this.get("content");
var self = this;

View File

@ -1,13 +1,5 @@
import ObjectController from 'discourse/controllers/object';
/**
This controller previews an email digest
@class AdminEmailPreviewDigestController
@extends ObjectController
@namespace Discourse
@module Discourse
**/
export default ObjectController.extend({
actions: {

View File

@ -1,18 +1,10 @@
/**
This controller supports the interface for dealing with flags in the admin section.
@class AdminFlagsController
@extends Ember.Controller
@namespace Discourse
@module Discourse
**/
export default Ember.ArrayController.extend({
query: null,
adminOldFlagsView: Em.computed.equal("query", "old"),
adminActiveFlagsView: Em.computed.equal("query", "active"),
actions: {
disagreeFlags: function (flaggedPost) {
var self = this;
flaggedPost.disagreeFlags().then(function () {

View File

@ -1,14 +1,5 @@
/**
This controller supports the interface for listing screened email addresses in the admin section.
@class AdminLogsScreenedEmailsController
@extends Ember.ArrayController
@namespace Discourse
@module Discourse
**/
export default Ember.ArrayController.extend(Discourse.Presence, {
loading: false,
content: [],
actions: {
clearBlock: function(row){
@ -23,7 +14,7 @@ export default Ember.ArrayController.extend(Discourse.Presence, {
var self = this;
this.set('loading', true);
Discourse.ScreenedEmail.findAll().then(function(result) {
self.set('content', result);
self.set('model', result);
self.set('loading', false);
});
}

View File

@ -1,28 +1,19 @@
/**
This controller supports the interface for listing screened IP addresses in the admin section.
@class AdminLogsScreenedIpAddressesController
@extends Ember.ArrayController
@namespace Discourse
@module Discourse
**/
export default Ember.ArrayController.extend(Discourse.Presence, {
loading: false,
content: [],
itemController: 'admin-log-screened-ip-address',
show: function() {
var self = this;
this.set('loading', true);
Discourse.ScreenedIpAddress.findAll().then(function(result) {
self.set('content', result);
self.set('model', result);
self.set('loading', false);
});
},
actions: {
recordAdded: function(arg) {
this.get("content").unshiftObject(arg);
this.get("model").unshiftObject(arg);
}
}
});

View File

@ -1,20 +1,11 @@
/**
This controller supports the interface for listing screened URLs in the admin section.
@class AdminLogsScreenedUrlsController
@extends Ember.ArrayController
@namespace Discourse
@module Discourse
**/
export default Ember.ArrayController.extend(Discourse.Presence, {
loading: false,
content: [],
show: function() {
var self = this;
this.set('loading', true);
Discourse.ScreenedUrl.findAll().then(function(result) {
self.set('content', result);
self.set('model', result);
self.set('loading', false);
});
}

View File

@ -15,7 +15,7 @@ export default Ember.ArrayController.extend(Discourse.Presence, {
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('content', result);
self.set('model', result);
self.set('loading', false);
});
}.observes('filters.action_name', 'filters.acting_user', 'filters.target_user', 'filters.subject'),

View File

@ -10,7 +10,6 @@ export default Ember.ArrayController.extend(Discourse.Presence, {
username: null,
query: null,
selectAll: false,
content: null,
loading: false,
mustApproveUsers: Discourse.computed.setting('must_approve_users'),
@ -27,7 +26,7 @@ export default Ember.ArrayController.extend(Discourse.Presence, {
**/
selectAllChanged: function() {
var _this = this;
_.each(this.get('content'),function(user) {
_.each(this.get('model'),function(user) {
user.set('selected', _this.get('selectAll'));
});
}.observes('selectAll'),
@ -74,9 +73,9 @@ export default Ember.ArrayController.extend(Discourse.Presence, {
@property selectedCount
**/
selectedCount: function() {
if (this.blank('content')) return 0;
return this.get('content').filterProperty('selected').length;
}.property('content.@each.selected'),
if (this.blank('model')) return 0;
return this.get('model').filterProperty('selected').length;
}.property('model.@each.selected'),
/**
Do we have any selected users?
@ -95,7 +94,7 @@ export default Ember.ArrayController.extend(Discourse.Presence, {
adminUsersListController.set('loading', true);
Discourse.AdminUser.findAll(this.get('query'), { filter: this.get('username'), show_emails: showEmails }).then(function (result) {
adminUsersListController.set('content', result);
adminUsersListController.set('model', result);
adminUsersListController.set('loading', false);
});
},
@ -114,36 +113,28 @@ export default Ember.ArrayController.extend(Discourse.Presence, {
this.set('query', term);
},
/**
Approve all the currently selected users.
actions: {
approveUsers: function() {
Discourse.AdminUser.bulkApprove(this.get('model').filterProperty('selected'));
this.refreshUsers();
},
@method approveUsers
**/
approveUsers: function() {
Discourse.AdminUser.bulkApprove(this.get('content').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();
});
},
/**
Reject all the currently selected users.
@method rejectUsers
**/
rejectUsers: function() {
var controller = this;
Discourse.AdminUser.bulkReject(this.get('content').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);
showEmails: function() {
this.refreshUsers(true);
}
}
});

View File

@ -314,7 +314,7 @@ Discourse.AdminUser = Discourse.User.extend({
}
}.property('deleteForbidden'),
destroy: function() {
destroy: function(opts) {
var user = this;
var performDestroy = function(block) {
@ -324,6 +324,9 @@ Discourse.AdminUser = Discourse.User.extend({
formData["block_urls"] = true;
formData["block_ip"] = true;
}
if (opts && opts.deletePosts) {
formData["delete_posts"] = true;
}
Discourse.ajax("/admin/users/" + user.get('id') + '.json', {
type: 'DELETE',
data: formData

View File

@ -7,47 +7,38 @@
@module Discourse
**/
Discourse.SiteCustomization = Discourse.Model.extend({
trackedProperties: ['enabled', 'name', 'stylesheet', 'header', 'mobile_stylesheet', 'mobile_header', 'override_default_style'],
trackedProperties: ['enabled', 'name', 'stylesheet', 'header', 'footer', 'mobile_stylesheet', 'mobile_header', 'mobile_footer', 'override_default_style'],
description: function() {
return "" + this.name + (this.enabled ? ' (*)' : '');
}.property('selected', 'name'),
changed: function() {
var self = this;
var _this = this;
if(!this.originals) return false;
if (!this.originals) { return false; }
var changed = _.some(this.trackedProperties,function(p) {
return _this.originals[p] !== _this.get(p);
var changed = _.some(this.trackedProperties, function (p) {
return self.originals[p] !== self.get(p);
});
if(changed){
this.set('savingStatus','');
}
if (changed) { this.set('savingStatus', ''); }
return changed;
}.property('override_default_style', 'enabled', 'name', 'stylesheet', 'header', 'mobile_stylesheet', 'mobile_header', 'originals'),
}.property('override_default_style', 'enabled', 'name', 'stylesheet', 'header', 'footer', 'mobile_stylesheet', 'mobile_header', 'mobile_footer', 'originals'),
startTrackingChanges: function() {
var _this = this;
var self = this;
var originals = {};
_.each(this.trackedProperties,function(prop) {
originals[prop] = _this.get(prop);
return true;
_.each(this.trackedProperties, function (prop) {
originals[prop] = self.get(prop);
});
this.set('originals', originals);
}.on('init'),
previewUrl: function() {
return "/?preview-style=" + (this.get('key'));
}.property('key'),
disableSave: function() {
return !this.get('changed') || this.get('saving');
}.property('changed'),
previewUrl: function() { return "/?preview-style=" + this.get('key'); }.property('key'),
disableSave: function() { return !this.get('changed') || this.get('saving'); }.property('changed'),
save: function() {
this.set('savingStatus', I18n.t('saving'));
@ -57,8 +48,10 @@ Discourse.SiteCustomization = Discourse.Model.extend({
enabled: this.enabled,
stylesheet: this.stylesheet,
header: this.header,
footer: this.footer,
mobile_stylesheet: this.mobile_stylesheet,
mobile_header: this.mobile_header,
mobile_footer: this.mobile_footer,
override_default_style: this.override_default_style
};
@ -75,23 +68,19 @@ Discourse.SiteCustomization = Discourse.Model.extend({
siteCustomization.set('saving',false);
siteCustomization.startTrackingChanges();
});
},
destroy: function() {
if(!this.id) return;
return Discourse.ajax("/admin/site_customizations/" + this.id, {
type: 'DELETE'
});
if (!this.id) return;
return Discourse.ajax("/admin/site_customizations/" + this.id, { type: 'DELETE' });
}
});
var SiteCustomizations = Ember.ArrayProxy.extend({
selectedItemChanged: function() {
var selected = this.get('selectedItem');
_.each(this.get('content'),function(i) {
return i.set('selected', selected === i);
_.each(this.get('content'), function (i) {
i.set('selected', selected === i);
});
}.observes('selectedItem')
});

View File

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

View File

@ -0,0 +1,24 @@
export default Discourse.Route.extend({
model: function(params) {
this.filter = params.filter;
return Discourse.FlaggedPost.findAll(params.filter);
},
setupController: function(controller, model) {
controller.set('model', model);
controller.set('query', this.filter);
},
actions: {
showAgreeFlagModal: function (flaggedPost) {
Discourse.Route.showModal(this, 'admin_agree_flag', flaggedPost);
this.controllerFor('modal').set('modalClass', 'agree-flag-modal');
},
showDeleteFlagModal: function (flaggedPost) {
Discourse.Route.showModal(this, 'admin_delete_flag', flaggedPost);
this.controllerFor('modal').set('modalClass', 'delete-flag-modal');
}
}
});

View File

@ -1,41 +0,0 @@
Discourse.AdminFlagsIndexRoute = Discourse.Route.extend({
redirect: function() {
this.transitionTo('adminFlags.active');
}
});
Discourse.AdminFlagsRouteType = Discourse.Route.extend({
model: function() {
return Discourse.FlaggedPost.findAll(this.get('filter'));
},
setupController: function(controller, model) {
var adminFlagsController = this.controllerFor('adminFlags');
adminFlagsController.set('content', model);
adminFlagsController.set('query', this.get('filter'));
},
});
Discourse.AdminFlagsActiveRoute = Discourse.AdminFlagsRouteType.extend({
filter: 'active',
actions: {
showAgreeFlagModal: function (flaggedPost) {
Discourse.Route.showModal(this, 'admin_agree_flag', flaggedPost);
this.controllerFor('modal').set('modalClass', 'agree-flag-modal');
},
showDeleteFlagModal: function (flaggedPost) {
Discourse.Route.showModal(this, 'admin_delete_flag', flaggedPost);
this.controllerFor('modal').set('modalClass', 'delete-flag-modal');
}
}
});
Discourse.AdminFlagsOldRoute = Discourse.AdminFlagsRouteType.extend({
filter: 'old'
});

View File

@ -30,8 +30,7 @@ Discourse.Route.buildRoutes(function() {
this.resource('adminReports', { path: '/reports/:type' });
this.resource('adminFlags', { path: '/flags' }, function() {
this.route('active');
this.route('old');
this.route('list', { path: '/:filter' });
});
this.resource('adminLogs', { path: '/logs' }, function() {

View File

@ -2,71 +2,58 @@
<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>
<li><a {{action "selectStyle" this}} {{bind-attr class="this.selected:active"}}>{{this.description}}</a></li>
{{/each}}
</ul>
<button {{action "newCustomization"}} class='btn'><i class="fa fa-plus"></i>{{i18n admin.customize.new}}</button>
<button {{action "newCustomization"}} class='btn'>
{{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 class='current-style'>
{{#with selectedItem}}
{{text-field class="style-name" value=name}}
<div class='admin-controls'>
<ul class="nav nav-pills">
<li>
<a {{bind-attr class="view.stylesheetActive:active"}}{{action "selectStylesheet" href="true" target="view"}}>{{i18n admin.customize.css}}</a>
</li>
<li>
<a {{bind-attr class="view.headerActive:active"}}{{action "selectHeader" href="true" target="view"}}>{{i18n admin.customize.header}}</a>
</li>
<li>
<a {{bind-attr class="view.mobileStylesheetActive:active"}}{{action "selectMobileStylesheet" href="true" target="view"}}>{{i18n admin.customize.mobile_css}}</a>
</li>
<li>
<a {{bind-attr class="view.mobileHeaderActive:active"}}{{action "selectMobileHeader" href="true" target="view"}}>{{i18n admin.customize.mobile_header}}</a>
</li>
</ul>
<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>
</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}}
</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-container">
{{#if view.headerActive}}
{{aceEditor content=header mode="html"}}
{{/if}}
{{#if view.stylesheetActive}}
{{aceEditor content=stylesheet mode="scss"}}
{{/if}}
{{#if view.mobileHeaderActive}}
{{aceEditor content=mobile_header mode="html"}}
{{/if}}
{{#if view.mobileStylesheetActive}}
{{aceEditor content=mobile_stylesheet mode="scss"}}
{{/if}}
<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>
{{/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='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>
{{else}}
<p class="about">{{i18n admin.customize.about}}</p>
{{/if}}
<div class='clearfix'></div>

View File

@ -18,14 +18,10 @@
</div>
</div>
{{#if loading}}
{{loading-spinner}}
{{else}}
{{#loading-spinner condition=loading}}
{{#if showHtml}}
{{{html_content}}}
{{else}}
<pre>{{{text_content}}}</pre>
{{/if}}
{{/if}}
{{/loading-spinner}}

View File

@ -0,0 +1,153 @@
{{#if length}}
<table class='admin-flags'>
<thead>
<tr>
<th class='user'></th>
<th class='excerpt'></th>
<th class='flaggers'>{{i18n admin.flags.flagged_by}}</th>
<th class='flaggers'>{{#if adminOldFlagsView}}{{i18n admin.flags.resolved_by}}{{/if}}</th>
</tr>
</thead>
<tbody>
{{#each flaggedPost in content}}
<tr {{bind-attr class="flaggedPost.extraClasses"}}>
<td class='user'>
{{#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}}
{{/if}}
{{#if adminActiveFlagsView}}
{{#if flaggedPost.previous_flags_count}}
<span title="{{i18n admin.flags.previous_flags_count count=flaggedPost.previous_flags_count}}" class="badge-notification flagged-posts">{{flaggedPost.previous_flags_count}}</span>
{{/if}}
{{/if}}
</td>
<td class='excerpt'>
<h3>
{{#if flaggedPost.topic.isPrivateMessage}}
<span class="private-message-glyph">{{fa-icon envelope}}</span>
{{/if}}
{{topic-status topic=flaggedPost.topic}}
<a href='{{unbound flaggedPost.url}}'>{{flaggedPost.topic.title}}</a>
</h3>
{{#if flaggedPost.postAuthorFlagged}}
{{{flaggedPost.excerpt}}}
{{/if}}
</td>
<td class='flaggers'>
<table>
<tbody>
{{#each flaggedPost.flaggers}}
<tr>
<td class='avatar'>
{{#link-to 'adminUser' user}}
{{avatar user imageSize="small"}}
{{/link-to}}
</td>
<td>
{{#link-to 'adminUser' user}}
{{user.username}}
{{/link-to}}
{{format-age flaggedAt}}
<br />
{{flagType}}
</td>
</tr>
{{/each}}
</tbody>
</table>
</td>
<td class='flaggers result'>
{{#if adminOldFlagsView}}
<table>
<tbody>
{{#each flaggedPost.flaggers}}
<tr>
<td class='avatar'>
{{#link-to 'adminUser' disposedBy}}
{{avatar 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>
{{/if}}
</td>
</tr>
{{/each}}
</tbody>
</table>
{{/if}}
</td>
</tr>
{{#if flaggedPost.topicFlagged}}
<tr class='message'>
<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>
</div>
</td>
</tr>
{{/if}}
{{#each flaggedPost.conversations}}
<tr class='message'>
<td></td>
<td colspan="3">
<div>
{{#if response}}
<p>
{{#link-to 'adminUser' response.user}}{{avatar response.user imageSize="small"}}{{/link-to}}&nbsp;{{{response.excerpt}}}
</p>
{{#if 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>
{{/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>
{{/if}}
</div>
</td>
</tr>
{{/each}}
<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>
{{#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>
{{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>
{{/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>
{{/if}}
</td>
</tr>
{{/each}}
</tbody>
</table>
{{loading-spinner condition=view.loading}}
{{else}}
<p>{{i18n admin.flags.no_results}}</p>
{{/if}}

View File

@ -1,170 +1,12 @@
<div class='admin-controls'>
<div class='span15'>
<ul class="nav nav-pills">
<li>{{#link-to 'adminFlags.active'}}{{i18n admin.flags.active}}{{/link-to}}</li>
<li>{{#link-to 'adminFlags.old'}}{{i18n admin.flags.old}}{{/link-to}}</li>
<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>
<div class="admin-container">
{{#if loading}}
{{loading-spinner}}
{{else}}
{{#if length}}
<table class='admin-flags'>
<thead>
<tr>
<th class='user'></th>
<th class='excerpt'></th>
<th class='flaggers'>{{i18n admin.flags.flagged_by}}</th>
<th class='flaggers'>{{#if adminOldFlagsView}}{{i18n admin.flags.resolved_by}}{{/if}}</th>
</tr>
</thead>
<tbody>
{{#each flaggedPost in content}}
<tr {{bind-attr class="flaggedPost.extraClasses"}}>
<td class='user'>
{{#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}}
{{/if}}
{{#if adminActiveFlagsView}}
{{#if flaggedPost.previous_flags_count}}
<span title="{{i18n admin.flags.previous_flags_count count=flaggedPost.previous_flags_count}}" class="badge-notification flagged-posts">{{flaggedPost.previous_flags_count}}</span>
{{/if}}
{{/if}}
</td>
<td class='excerpt'>
<h3>
{{#if flaggedPost.topic.isPrivateMessage}}
<span class="private-message-glyph">{{fa-icon envelope}}</span>
{{/if}}
{{topic-status topic=flaggedPost.topic}}
<a href='{{unbound flaggedPost.url}}'>{{flaggedPost.topic.title}}</a>
</h3>
{{#if flaggedPost.postAuthorFlagged}}
{{{flaggedPost.excerpt}}}
{{/if}}
</td>
<td class='flaggers'>
<table>
<tbody>
{{#each flaggedPost.flaggers}}
<tr>
<td class='avatar'>
{{#link-to 'adminUser' user}}
{{avatar user imageSize="small"}}
{{/link-to}}
</td>
<td>
{{#link-to 'adminUser' user}}
{{user.username}}
{{/link-to}}
{{format-age flaggedAt}}
<br />
{{flagType}}
</td>
</tr>
{{/each}}
</tbody>
</table>
</td>
<td class='flaggers result'>
{{#if adminOldFlagsView}}
<table>
<tbody>
{{#each flaggedPost.flaggers}}
<tr>
<td class='avatar'>
{{#link-to 'adminUser' disposedBy}}
{{avatar 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>
{{/if}}
</td>
</tr>
{{/each}}
</tbody>
</table>
{{/if}}
</td>
</tr>
{{#if flaggedPost.topicFlagged}}
<tr class='message'>
<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>
</div>
</td>
</tr>
{{/if}}
{{#each flaggedPost.conversations}}
<tr class='message'>
<td></td>
<td colspan="3">
<div>
{{#if response}}
<p>
{{#link-to 'adminUser' response.user}}{{avatar response.user imageSize="small"}}{{/link-to}}&nbsp;{{{response.excerpt}}}
</p>
{{#if 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>
{{/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>
{{/if}}
</div>
</td>
</tr>
{{/each}}
<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>
{{#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>
{{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>
{{/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>
{{/if}}
</td>
</tr>
{{/each}}
</tbody>
</table>
{{#if view.loading}}
{{loading-spinner}}
{{/if}}
{{else}}
<p>{{i18n admin.flags.no_results}}</p>
{{/if}}
{{/if}}
{{outlet}}
</div>

View File

@ -1,8 +1,6 @@
<p>{{i18n admin.logs.screened_emails.description}}</p>
{{#if loading}}
{{loading-spinner}}
{{else}}
{{#loading-spinner condition=loading}}
{{#if model.length}}
<div class='table screened-emails'>
@ -23,4 +21,4 @@
{{else}}
{{i18n search.no_results}}
{{/if}}
{{/if}}
{{/loading-spinner}}

View File

@ -3,9 +3,7 @@
{{screened-ip-address-form action="recordAdded"}}
<br/>
{{#if loading}}
{{loading-spinner}}
{{else}}
{{#loading-spinner condition=loading}}
{{#if model.length}}
<div class='table admin-logs-table screened-ip-addresses'>
@ -25,4 +23,4 @@
{{else}}
{{i18n search.no_results}}
{{/if}}
{{/if}}
{{/loading-spinner}}

View File

@ -1,10 +1,7 @@
<p>{{i18n admin.logs.screened_urls.description}}</p>
{{#if loading}}
{{loading-spinner}}
{{else}}
{{#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>
@ -17,8 +14,7 @@
{{view 'screened-urls-list' content=controller}}
</div>
{{else}}
{{i18n search.no_results}}
{{/if}}
{{/if}}
{{/loading-spinner}}

View File

@ -43,14 +43,11 @@
<div class="clearfix"></div>
</div>
{{#if loading}}
<br/>
{{loading-spinner}}
{{else}}
{{#loading-spinner condition=loading}}
{{#if model.length}}
{{view "staff-action-logs-list" content=controller}}
{{else}}
{{i18n search.no_results}}
{{/if}}
{{/if}}
{{/loading-spinner}}
</div>

View File

@ -3,7 +3,7 @@
<div>
{{i18n admin.dashboard.reports.start_date}} {{input type="date" value=startDate}}
{{i18n admin.dashboard.reports.end_date}} {{input type="date" value=endDate}}
<button {{action refreshReport}} class='btn btn-primary'>{{i18n admin.dashboard.reports.refresh_report}}</button>
<button {{action "refreshReport"}} class='btn btn-primary'>{{i18n admin.dashboard.reports.refresh_report}}</button>
</div>
<div class='view-options'>
@ -20,9 +20,7 @@
{{/if}}
</div>
{{#if refreshing}}
{{loading-spinner}}
{{else}}
{{#loading-spinner condition=refreshing}}
<table class='table report'>
<tr>
<th>{{xaxis}}</th>
@ -45,4 +43,4 @@
</tr>
{{/each}}
</table>
{{/if}}
{{/loading-spinner}}

View File

@ -14,15 +14,14 @@
<div class="site-settings-nav pull-left">
<ul class="nav nav-stacked">
{{#each category in controller}}
<li {{bind-attr class="category.nameKey"}}>
{{#link-to 'adminSiteSettingsCategory' category.nameKey tagName='li' class=category.nameKey}}
{{#link-to 'adminSiteSettingsCategory' category.nameKey class=category.nameKey}}
{{category.name}}
{{#if filtered}}
<span class="count">({{category.siteSettings.length}})</span>
{{/if}}
<span class='fa fa-chevron-right'></span>
{{/link-to}}
</li>
{{/link-to}}
{{/each}}
</ul>
</div>

View File

@ -6,9 +6,7 @@
</div>
</div>
{{#if loading}}
{{loading-spinner}}
{{else}}
{{#loading-spinner condition=loading}}
<div class='admin-container user-badges'>
<h2>{{i18n admin.badges.grant_badge}}</h2>
{{#if noBadges}}
@ -56,4 +54,4 @@
{{/each}}
</table>
</div>
{{/if}}
{{/loading-spinner}}

View File

@ -37,9 +37,7 @@
</div>
</div>
{{#if loading}}
{{loading-spinner}}
{{else}}
{{#loading-spinner condition=loading}}
{{#if model.length}}
<table class='table'>
<tr>
@ -101,5 +99,5 @@
{{else}}
<p>{{i18n search.no_results}}</p>
{{/if}}
{{/if}}
{{/loading-spinner}}
</div>

View File

@ -1,5 +1,6 @@
Discourse.AdminBackupsLogsView = Discourse.View.extend({
import { renderSpinner } from 'discourse/helpers/loading-spinner';
export default Discourse.View.extend({
classNames: ["admin-backups-logs"],
_initialize: function() { this._reset(); }.on("init"),
@ -38,7 +39,7 @@ Discourse.AdminBackupsLogsView = Discourse.View.extend({
}
// add a loading indicator
if (this.get("controller.status.isOperationRunning")) {
buffer.push("<i class='fa fa-spinner fa-spin'></i>");
buffer.push(renderSpinner('small'));
}
},

View File

@ -1,12 +1,10 @@
Discourse.AdminFlagsView = Discourse.View.extend(Discourse.LoadMore, {
export default Discourse.View.extend(Discourse.LoadMore, {
loading: false,
eyelineSelector: '.admin-flags tbody tr',
actions: {
loadMore: function() {
var self = this;
if (this.get("loading") || this.get("model.allLoaded")) { return; }
this.set("loading", true);
@ -15,7 +13,6 @@ Discourse.AdminFlagsView = Discourse.View.extend(Discourse.LoadMore, {
self.set("loading", false);
});
}
}
});

View File

@ -12,15 +12,20 @@ Discourse.AdminCustomizeView = Discourse.View.extend({
templateName: 'admin/templates/customize',
classNames: ['customize'],
selected: 'stylesheet',
headerActive: Em.computed.equal('selected', 'header'),
footerActive: Em.computed.equal('selected', 'footer'),
stylesheetActive: Em.computed.equal('selected', 'stylesheet'),
mobileHeaderActive: Em.computed.equal('selected', 'mobileHeader'),
mobileFooterActive: Em.computed.equal('selected', 'mobileFooter'),
mobileStylesheetActive: Em.computed.equal('selected', 'mobileStylesheet'),
actions: {
selectHeader: function() { this.set('selected', 'header'); },
selectFooter: function() { this.set('selected', 'footer'); },
selectStylesheet: function() { this.set('selected', 'stylesheet'); },
selectMobileHeader: function() { this.set('selected', 'mobileHeader'); },
selectMobileFooter: function() { this.set('selected', 'mobileFooter'); },
selectMobileStylesheet: function() { this.set('selected', 'mobileStylesheet'); }
},

View File

@ -1,29 +0,0 @@
Discourse.AdminIpLocatorView = Discourse.View.extend({
templateName: 'admin/templates/ip_locator',
classNames: ["iplocator"],
actions: {
hideBox: function(){
this.set("showBox", false);
},
lookup: function(){
if (!this.get("location")){
$.get("http://ipinfo.io/" + this.get("ip"), function(response) {
this.set("location", response);
}.bind(this), "jsonp");
}
if (!this.get("other_accounts")){
this.set("other_accounts_loading", true);
Discourse.ajax("/admin/users/list/active.json", {
data: {"ip": this.get("ip"),
"exclude": this.get("controller.id")
}
}).then(function (users) {
this.set("other_accounts", users.map(function(u) { return Discourse.AdminUser.create(u);}));
this.set("other_accounts_loading", false);
}.bind(this));
}
this.set("showBox", true);
}
}
});

View File

@ -1,11 +1,3 @@
/**
A modal view for agreeing with a flag.
@class AdminAgreeFlagView
@extends Discourse.ModalBodyView
@namespace Discourse
@module Discourse
**/
Discourse.AdminAgreeFlagView = Discourse.ModalBodyView.extend({
templateName: 'admin/templates/modal/admin_agree_flag',
title: I18n.t('admin.flags.agree_flag_modal_title')

View File

@ -7,6 +7,7 @@
@module Discourse
**/
export default Ember.Component.extend({
loading: Ember.computed.not('loaded'),
loaded: function() {
var topicList = this.get('topicList');

View File

@ -0,0 +1,8 @@
export default Ember.Component.extend({
classNameBindings: ['containerClass'],
layoutName: 'components/conditional-loading-spinner',
containerClass: function() {
return (this.get('size') === 'small') ? 'inline-spinner' : undefined;
}.property('size')
});

View File

@ -2,14 +2,14 @@ import ObjectController from 'discourse/controllers/object';
import TopPeriod from 'discourse/models/top-period';
export default ObjectController.extend({
needs: ['navigation/category'],
needs: ['navigation/category', 'discovery/topics'],
loading: false,
loadingSpinner: false,
scheduledSpinner: null,
category: Em.computed.alias('controllers.navigation/category.category'),
noSubcategories: Em.computed.alias('controllers.navigation/category.noSubcategories'),
loadedAllItems: Em.computed.not("controllers.discovery/topics.canLoadMore"),
showMoreUrl: function(period) {
var url = '', category = this.get('category');
if (category) {

View File

@ -13,7 +13,10 @@ export default DiscoveryController.extend({
// Don't refresh if we're still loading
if (this.get('controllers.discovery.loading')) { return; }
this.send('loading');
// If we `send('loading')` here, due to returning true it bubbles up to the
// router and ember throws an error due to missing `handlerInfos`.
// Lesson learned: Don't call `loading` yourself.
this.set('controllers.discovery.loading', true);
Discourse.CategoryList.list('categories').then(function(list) {
self.set('model', list);
self.send('loadingComplete');

View File

@ -42,7 +42,10 @@ var controllerOpts = {
// Don't refresh if we're still loading
if (this.get('controllers.discovery.loading')) { return; }
this.send('loading');
// If we `send('loading')` here, due to returning true it bubbles up to the
// router and ember throws an error due to missing `handlerInfos`.
// Lesson learned: Don't call `loading` yourself.
this.set('controllers.discovery.loading', true);
Discourse.TopicList.find(filter).then(function(list) {
self.setProperties({ model: list, selected: [] });

View File

@ -48,7 +48,7 @@ export default ObjectController.extend(Discourse.SelectedPostsCount, {
if (arguments.length > 1) {
postStream.set('show_deleted', value);
}
return postStream.get('show_deleted') ? true : null;
return postStream.get('show_deleted') ? true : undefined;
}.property('postStream.summary'),
filter: function(key, value) {
@ -58,10 +58,18 @@ export default ObjectController.extend(Discourse.SelectedPostsCount, {
if (arguments.length > 1) {
postStream.set('summary', value === "summary");
}
return postStream.get('summary') ? "summary" : null;
return postStream.get('summary') ? "summary" : undefined;
}.property('postStream.summary'),
username_filters: Discourse.computed.queryAlias('postStream.streamFilters.username_filters'),
username_filters: function(key, value) {
var postStream = this.get('postStream');
if (!postStream) { return; }
if (arguments.length > 1) {
postStream.set('streamFilters.username_filters', value);
}
return postStream.get('streamFilters.username_filters');
}.property('postStream.streamFilters.username_filters'),
init: function() {
this._super();
@ -361,6 +369,11 @@ export default ObjectController.extend(Discourse.SelectedPostsCount, {
this.get('content').clearPin();
},
togglePinnedForUser: function() {
if (this.get('pinned_at'))
this.get('pinned') ? this.get('content').clearPin() : this.get('content').rePin();
},
replyAsNewTopic: function(post) {
var composerController = this.get('controllers.composer'),
quoteController = this.get('controllers.quote-button'),
@ -429,6 +442,10 @@ export default ObjectController.extend(Discourse.SelectedPostsCount, {
}
},
togglePinnedState: function() {
this.send('togglePinnedForUser');
},
showExpandButton: function() {
var post = this.get('post');
return post.get('post_number') === 1 && post.get('topic.expandable_first_post');
@ -478,6 +495,7 @@ export default ObjectController.extend(Discourse.SelectedPostsCount, {
}.property('selectedPostsCount'),
hasError: Ember.computed.or('notFoundHtml', 'message'),
noErrorYet: Ember.computed.not('hasError'),
multiSelectChanged: function() {
// Deselect all posts when multi select is turned off

View File

@ -78,6 +78,7 @@ export default ObjectController.extend({
Discourse.User.findByUsername(username).then(function (user) {
self.setProperties({ user: user, avatar: user, visible: true});
self.appEvents.trigger('usercard:shown');
}).finally(function(){
self.set('userLoading', null);
});

View File

@ -3,6 +3,7 @@ export default Ember.ArrayController.extend({
needs: ['user-notifications'],
canLoadMore: true,
loading: false,
showDismissButton: function() {
return this.get('user').total_unread_notifications > 0;
}.property('user'),
@ -26,7 +27,7 @@ export default Ember.ArrayController.extend({
var notifications = result.get('content');
self.pushObjects(notifications);
// Stop trying if it's the end
if (notifications && notifications.length === 0) {
if (notifications && (notifications.length === 0 || notifications.length < 60)) {
self.set('canLoadMore', false);
}
}).catch(function(error) {

View File

@ -2,6 +2,8 @@ import ObjectController from 'discourse/controllers/object';
import CanCheckEmails from 'discourse/mixins/can-check-emails';
export default ObjectController.extend(CanCheckEmails, {
indexStream: true,
needs: ['user-notifications', 'user_topics_list'],
viewingSelf: function() {
return this.get('content.username') === Discourse.User.currentProp('username');
@ -32,16 +34,44 @@ export default ObjectController.extend(CanCheckEmails, {
(this.get('userActionType') === Discourse.UserAction.TYPES.messages_received);
}.property('userActionType'),
/**
Can the currently logged in user invite users to the site
@property canInviteToForum
**/
canInviteToForum: function() {
return Discourse.User.currentProp('can_invite_to_forum');
}.property(),
canDeleteUser: function() {
return this.get('can_be_deleted') && this.get('can_delete_all_posts');
}.property('can_be_deleted', 'can_delete_all_posts'),
loadedAllItems: function() {
switch (this.get("datasource")) {
case "badges": { return true; }
case "notifications": { return !this.get("controllers.user-notifications.canLoadMore"); }
case "topic_list": { return !this.get("controllers.user_topics_list.canLoadMore"); }
case "stream": {
if (this.get("userActionType")) {
var stat = _.find(this.get("stats"), { action_type: this.get("userActionType") });
return stat && stat.count <= this.get("stream.itemsLoaded");
} else {
return this.get("statsCountNonPM") <= this.get("stream.itemsLoaded");
}
}
}
return false;
}.property("datasource",
"userActionType", "stats", "stream.itemsLoaded",
"controllers.user_topics_list.canLoadMore",
"controllers.user-notifications.canLoadMore"),
privateMessagesActive: Em.computed.equal('pmView', 'index'),
privateMessagesMineActive: Em.computed.equal('pmView', 'mine'),
privateMessagesUnreadActive: Em.computed.equal('pmView', 'unread')
privateMessagesUnreadActive: Em.computed.equal('pmView', 'unread'),
actions: {
adminDelete: function() {
Discourse.AdminUser.find(this.get('username').toLowerCase()).then(function(user){
user.destroy({deletePosts: true});
});
}
}
});

View File

@ -3,12 +3,12 @@
**/
var acceptableCodeClasses =
["lang-auto", "1c", "actionscript", "apache", "applescript", "avrasm", "axapta", "bash", "brainfuck",
["auto", "1c", "actionscript", "apache", "applescript", "avrasm", "axapta", "bash", "brainfuck",
"clojure", "cmake", "coffeescript", "cpp", "cs", "css", "d", "delphi", "diff", "xml", "django", "dos",
"erlang-repl", "erlang", "glsl", "go", "handlebars", "haskell", "http", "ini", "java", "javascript",
"json", "lisp", "lua", "markdown", "matlab", "mel", "nginx", "objectivec", "parser3", "perl", "php",
"profile", "python", "r", "rib", "rsl", "ruby", "rust", "scala", "smalltalk", "sql", "tex", "text",
"vala", "vbscript", "vhdl"];
"json", "lisp", "lua", "markdown", "matlab", "mel", "nginx", "nohighlight", "objectivec", "parser3",
"perl", "php", "profile", "python", "r", "rib", "rsl", "ruby", "rust", "scala", "smalltalk", "sql",
"tex", "text", "vala", "vbscript", "vhdl"];
var textCodeClasses = ["text", "pre"];
@ -32,9 +32,9 @@ Discourse.Dialect.replaceBlock({
}
if (textCodeClasses.indexOf(matches[1]) !== -1) {
return ['p', ['pre', ['code', flattenBlocks(blockContents) ]]];
return ['p', ['pre', ['code', {'class': 'lang-nohighlight'}, flattenBlocks(blockContents) ]]];
} else {
return ['p', ['pre', ['code', {'class': klass}, flattenBlocks(blockContents) ]]];
return ['p', ['pre', ['code', {'class': 'lang-' + klass}, flattenBlocks(blockContents) ]]];
}
}
});
@ -69,3 +69,7 @@ Discourse.Dialect.replaceBlock({
return ['p', ['pre', flattenBlocks(blockContents)]];
}
});
// Whitelist the language classes
var regexpSource = "^lang-(" + acceptableCodeClasses.join('|') + ")$";
Discourse.Markdown.whiteListTag('code', 'class', new RegExp(regexpSource, "i"));

View File

@ -3,6 +3,24 @@
var classify = Ember.String.classify;
var get = Ember.get;
var LOADING_WHITELIST = ['badges', 'userActivity', 'userPrivateMessages', 'admin', 'adminFlags',
'user', 'preferences', 'adminEmail'],
_dummyRoute,
_loadingView;
function loadingResolver(cb) {
return function(parsedName) {
var fullNameWithoutType = parsedName.fullNameWithoutType;
if (fullNameWithoutType.indexOf('Loading') >= 0) {
fullNameWithoutType = fullNameWithoutType.replace('Loading', '');
if (LOADING_WHITELIST.indexOf(fullNameWithoutType) !== -1) {
return cb(fullNameWithoutType);
}
}
};
}
function parseName(fullName) {
/*jshint validthis:true */
@ -66,7 +84,7 @@ export default Ember.DefaultResolver.extend({
},
resolveView: function(parsedName) {
return this.customResolve(parsedName) || this._super(parsedName);
return this.findLoadingView(parsedName) || this.customResolve(parsedName) || this._super(parsedName);
},
resolveHelper: function(parsedName) {
@ -82,16 +100,9 @@ export default Ember.DefaultResolver.extend({
},
resolveRoute: function(parsedName) {
return this.customResolve(parsedName) || this._super(parsedName);
return this.findLoadingRoute(parsedName) || this.customResolve(parsedName) || this._super(parsedName);
},
/**
Attaches a view and wires up the container properly
@method resolveTemplate
@param {String} parsedName the name of the template we want to resolve
@returns {Template} the template (if found)
**/
resolveTemplate: function(parsedName) {
return this.findPluginTemplate(parsedName) ||
this.findMobileTemplate(parsedName) ||
@ -99,6 +110,19 @@ export default Ember.DefaultResolver.extend({
Ember.TEMPLATES.not_found;
},
findLoadingRoute: loadingResolver(function() {
_dummyRoute = _dummyRoute || Ember.Route.extend();
return _dummyRoute;
}),
findLoadingView: loadingResolver(function() {
if (!_loadingView) {
_loadingView = require('discourse/views/loading', null, null, true /* force sync */);
if (_loadingView && _loadingView['default']) { _loadingView = _loadingView['default']; }
}
return _loadingView;
}),
findPluginTemplate: function(parsedName) {
var pluginParsedName = this.parseName(parsedName.fullName.replace("template:", "template:javascripts/"));
return this.findTemplate(pluginParsedName);

View File

@ -1,139 +0,0 @@
var DiscourseGroupedEach = function(context, path, options) {
var self = this,
normalized = Ember.Handlebars.normalizePath(context, path, options.data);
this.context = context;
this.path = path;
this.options = options;
this.template = options.fn;
this.containingView = options.data.view;
this.normalizedRoot = normalized.root;
this.normalizedPath = normalized.path;
this.content = this.lookupContent();
this.destroyed = false;
this.addContentObservers();
this.addArrayObservers();
this.containingView.on('willClearRender', function() {
self.destroy();
});
};
DiscourseGroupedEach.prototype = {
contentWillChange: function() {
this.removeArrayObservers();
},
contentDidChange: function() {
this.content = this.lookupContent();
this.addArrayObservers();
this.rerenderContainingView();
},
contentArrayWillChange: Ember.K,
contentArrayDidChange: function() {
this.rerenderContainingView();
},
lookupContent: function() {
return Ember.Handlebars.get(this.normalizedRoot, this.normalizedPath, this.options);
},
addArrayObservers: function() {
if (!this.content) { return; }
this.content.addArrayObserver(this, {
willChange: 'contentArrayWillChange',
didChange: 'contentArrayDidChange'
});
},
removeArrayObservers: function() {
if (!this.content) { return; }
this.content.removeArrayObserver(this, {
willChange: 'contentArrayWillChange',
didChange: 'contentArrayDidChange'
});
},
addContentObservers: function() {
Ember.addBeforeObserver(this.normalizedRoot, this.normalizedPath, this, this.contentWillChange);
Ember.addObserver(this.normalizedRoot, this.normalizedPath, this, this.contentDidChange);
},
removeContentObservers: function() {
Ember.removeBeforeObserver(this.normalizedRoot, this.normalizedPath, this.contentWillChange);
Ember.removeObserver(this.normalizedRoot, this.normalizedPath, this.contentDidChange);
},
render: function() {
if (!this.content) { return; }
var content = this.content,
contentLength = Em.get(content, 'length'),
data = this.options.data,
template = this.template,
keyword = this.options.hash.keyword;
data.insideEach = true;
for (var i = 0; i < contentLength; i++) {
var row = content.objectAt(i);
if (keyword) {
data.keywords = data.keywords || {};
data.keywords[keyword] = row;
}
template(row, { data: data });
}
},
rerenderContainingView: function() {
var self = this;
Ember.run.scheduleOnce('render', this, function() {
// It's possible it's been destroyed after we enqueued a re-render call.
if (!self.destroyed) {
self.containingView.rerender();
}
});
},
destroy: function() {
this.removeContentObservers();
if (this.content) {
this.removeArrayObservers();
}
this.destroyed = true;
}
};
function groupedEachHelper(path, options) {
if (arguments.length === 4) {
Ember.assert("If you pass more than one argument to the groupedEach helper, it must be in the form #groupedEach foo in bar", arguments[1] === "in");
var keywordName = arguments[0];
options = arguments[3];
path = arguments[2];
if (path === '') { path = "this"; }
options.hash.keyword = keywordName;
}
if (arguments.length === 1) {
options = path;
path = 'this';
}
options.hash.dataSourceBinding = path;
options.data.insideGroup = true;
new DiscourseGroupedEach(this, path, options).render();
}
Ember.Handlebars.registerHelper('groupedEach', function() {
Em.warn("The `groupedEach` helper is deprecated. Use `grouped-each` instead.");
return groupedEachHelper.apply(this, Array.prototype.slice.apply(arguments));
});
Ember.Handlebars.registerHelper('grouped-each', groupedEachHelper);

View File

@ -1,7 +1,31 @@
var spinnerHTML = "<div class='spinner'><div class='spinner-container container1'><div class='circle1'></div><div class='circle2'></div><div class='circle3'></div><div class='circle4'></div></div><div class='spinner-container container2'><div class='circle1'></div><div class='circle2'></div><div class='circle3'></div><div class='circle4'></div></div><div class='spinner-container container3'><div class='circle1'></div><div class='circle2'></div><div class='circle3'></div><div class='circle4'></div></div></div>";
import ConditionalLoadingSpinner from 'discourse/components/conditional-loading-spinner';
Handlebars.registerHelper('loading-spinner', function() {
return new Handlebars.SafeString(spinnerHTML);
function renderSpinner(cssClass) {
var html = "<div class='spinner";
if (cssClass) { html += ' ' + cssClass; }
return html + "'></div>";
}
var spinnerHTML = renderSpinner();
/**
If you use it as a regular helper {{loading-spinner}} you'll just get the
HTML for a spinner.
If you provide an `condition=xyz` parameter, it will be bound to that property
and only show when it's truthy.
If you use the block form `{{#loading-spinner}} ... {{/loading-spinner}`,
the contents will shown when the loading condition finishes.
**/
Handlebars.registerHelper('loading-spinner', function(options) {
var hash = options.hash;
if (hash && hash.condition) {
var types = options.hashTypes;
Discourse.Utilities.normalizeHash(hash, types);
return Ember.Handlebars.helpers.view.call(this, ConditionalLoadingSpinner, options);
} else {
return new Handlebars.SafeString(renderSpinner((hash && hash.size) ? hash.size : undefined));
}
});
export { spinnerHTML };
export { spinnerHTML, renderSpinner };

View File

@ -3,7 +3,10 @@ export function renderAvatar(user, options) {
if (user) {
var username = Em.get(user, 'username');
if (!username) username = Em.get(user, options.usernamePath);
if (!username) {
if (!options.usernamePath) { return ''; }
username = Em.get(user, options.usernamePath);
}
var title;
if (!options.ignoreTitle) {

View File

@ -112,24 +112,6 @@ Discourse.computed = {
return computed.property.apply(computed, args);
},
/**
Creates a one way alias to a computed property, suitable for query params.
@method queryAlias
@param {String} path to the alias
@param {String} defaultValue for the variable (omitted if equal)
**/
queryAlias: function(path, defaultValue) {
return Em.computed(function(key, value) {
if (value) {
// Annoying but this ensures the parameter is present
}
var result = this.get(path);
if (typeof result !== "undefined" && result.toString() === defaultValue) { return; }
return result;
}).property(path);
},
/**
Creates a property from a SiteSetting. In the future the plan is for them to
be able to update when changed.

View File

@ -95,8 +95,8 @@ updateRelativeAge = function(elems) {
};
autoUpdatingRelativeAge = function(date,options) {
if (!date) return "";
if (+date === +new Date(0)) return "";
options = options || {};
var format = options.format || "tiny";

View File

@ -52,6 +52,7 @@ Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({
'shift+j': 'nextSection',
'j': 'selectDown',
'shift+k': 'prevSection',
'shift+p': 'pinUnpinTopic',
'k': 'selectUp',
'u': 'goBack',
'/': 'showSearch',
@ -131,7 +132,11 @@ Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({
},
createTopic: function() {
Discourse.__container__.lookup('controller:composer').open({action: Discourse.Composer.CREATE_TOPIC, draftKey: Discourse.Composer.DRAFT});
Discourse.__container__.lookup('controller:composer').open({action: Discourse.Composer.CREATE_TOPIC, draftKey: Discourse.Composer.CREATE_TOPIC});
},
pinUnpinTopic: function() {
Discourse.__container__.lookup('controller:topic').togglePinnedState();
},
toggleProgress: function() {
@ -240,7 +245,7 @@ Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({
if ($article.is('.topic-post')) {
var tabLoc = $article.find('a.tabLoc');
if (tabLoc.length === 0) {
tabLoc = $('<a href="#" class="tabLoc"></a>');
tabLoc = $('<a href class="tabLoc"></a>');
$article.prepend(tabLoc);
}
tabLoc.focus();

View File

@ -1,4 +1,4 @@
/*global Markdown:true, hljs:true */
/*global Markdown, console */
/**
Contains methods to help us with markdown formatting.
@ -7,10 +7,44 @@
@namespace Discourse
@module Discourse
**/
var _validClasses = {},
_validIframes = [],
_validTags = {},
_decoratedCaja = false;
/**
* An object mapping from HTML tag names to an object mapping the valid
* attributes on that tag to an array of permitted values.
*
* The permitted values can be strings or regexes.
*
* The pseduo-attribute 'data-*' can be used to validate any data-foo
* attributes without any specified validations.
*
* Code can insert into this map by calling Discourse.Markdown.whiteListTag().
*
* Example:
*
* <pre><code>
* {
* a: {
* href: ['*'],
* data-mention-id: [/^\d+$/],
* ...
* },
* code: {
* class: ['ada', 'haskell', 'c', 'cpp', ... ]
* },
* ...
* }
* </code></pre>
*
* @private
*/
var _validTags = {};
/**
* Classes valid on all elements. Map from class name to 'true'.
* @private
*/
var _validClasses = {};
var _validIframes = [];
var _decoratedCaja = false;
function validateAttribute(tagName, attribName, value) {
var tag = _validTags[tagName];
@ -18,18 +52,17 @@ function validateAttribute(tagName, attribName, value) {
// Handle classes
if (attribName === "class") {
if (_validClasses[value]) { return value; }
}
if (tag) {
var classes = tag['class'];
if (classes && (classes.indexOf(value) !== -1 || classes.indexOf('*') !== -1)) {
return value;
}
}
} else if (attribName.indexOf('data-') === 0) {
// data-* attributes
if (tag) {
var allowed = tag[attribName] || tag['data-*'];
if (allowed && (allowed === value || allowed.indexOf('*') !== -1)) { return value; }
if (attribName.indexOf('data-') === 0) {
// data-* catch-all validators
if (tag && tag['data-*'] && !tag[attribName]) {
var permitted = tag['data-*'];
if (permitted && (
permitted.indexOf(value) !== -1 ||
permitted.indexOf('*') !== -1 ||
((permitted instanceof RegExp) && permitted.test(value)))
) { return value; }
}
}
@ -37,21 +70,45 @@ function validateAttribute(tagName, attribName, value) {
var attrs = tag[attribName];
if (attrs && (attrs.indexOf(value) !== -1 ||
attrs.indexOf('*') !== -1) ||
_.any(attrs,function(r){return (r instanceof RegExp) && value.search(r) >= 0;})
_.any(attrs, function(r) { return (r instanceof RegExp) && r.test(value); })
) { return value; }
}
// return undefined;
}
function anchorRegexp(regex) {
if (/^\^.*\$$/.test(regex.source)) {
return regex; // already anchored
}
var flags = "";
if (regex.global) {
if (typeof console !== 'undefined') {
console.warn("attribute validation regex should not be global");
}
}
if (regex.ignoreCase) { flags += "i"; }
if (regex.multiline) { flags += "m"; }
if (regex.sticky) { throw "Invalid attribute validation regex - cannot be sticky"; }
return new RegExp("^" + regex.source + "$", flags);
}
Discourse.Markdown = {
/**
Whitelist class for only a certain tag
Add to the attribute whitelist for a certain HTML tag.
@param {String} tagName to whitelist
@param {String} attribName to whitelist
@param {String} value to whitelist
@param {String} tagName tag to whitelist the attr for
@param {String} attribName attr to whitelist for the tag
@param {String | RegExp} [value] whitelisted value for the attribute
**/
whiteListTag: function(tagName, attribName, value) {
if (value instanceof RegExp) {
value = anchorRegexp(value);
}
_validTags[tagName] = _validTags[tagName] || {};
_validTags[tagName][attribName] = _validTags[tagName][attribName] || [];
_validTags[tagName][attribName].push(value || '*');
@ -238,26 +295,19 @@ Discourse.Markdown = {
RSVP.EventTarget.mixin(Discourse.Markdown);
Discourse.Markdown.whiteListTag('a', 'class', 'attachment');
Discourse.Markdown.whiteListTag('a', 'target', '_blank');
Discourse.Markdown.whiteListTag('a', 'class', 'onebox');
Discourse.Markdown.whiteListTag('a', 'class', 'mention');
Discourse.Markdown.whiteListTag('a', 'target', '_blank');
Discourse.Markdown.whiteListTag('a', 'rel', 'nofollow');
Discourse.Markdown.whiteListTag('a', 'data-bbcode');
Discourse.Markdown.whiteListTag('a', 'name');
Discourse.Markdown.whiteListTag('img', 'src', /^data:image.*/i);
Discourse.Markdown.whiteListTag('img', 'src', /^data:image.*$/i);
Discourse.Markdown.whiteListTag('div', 'class', 'title');
Discourse.Markdown.whiteListTag('div', 'class', 'quote-controls');
// explicitly whitelist classes we need allowed through for
// syntax highlighting, grabbed from highlight.js
hljs.listLanguages().forEach(function (language) {
Discourse.Markdown.whiteListTag('code', 'class', language);
});
Discourse.Markdown.whiteListTag('code', 'class', 'text');
Discourse.Markdown.whiteListTag('code', 'class', 'lang-auto');
Discourse.Markdown.whiteListTag('span', 'class', 'mention');
Discourse.Markdown.whiteListTag('span', 'class', 'spoiler');
Discourse.Markdown.whiteListTag('div', 'class', 'spoiler');
@ -273,3 +323,4 @@ Discourse.Markdown.whiteListTag('span', 'bbcode-s');
Discourse.Markdown.whiteListTag('span', 'class', 'excerpt');
Discourse.Markdown.whiteListIframe(/^(https?:)?\/\/www\.google\.com\/maps\/embed\?.+/i);
Discourse.Markdown.whiteListIframe(/^(https?:)?\/\/www\.openstreetmap\.org\/export\/embed.html\?.+/i);

View File

@ -65,8 +65,8 @@ Discourse.Post = Discourse.Model.extend({
postElementId: Discourse.computed.fmt('post_number', 'post_%@'),
canViewRawEmail: function() {
return Discourse.User.currentProp('staff');
}.property(),
return this.get("user_id") === Discourse.User.currentProp("id") || Discourse.User.currentProp('staff');
}.property("user_id"),
bookmarkedChanged: function() {
Discourse.Post.bookmark(this.get('id'), this.get('bookmarked'))

View File

@ -31,6 +31,8 @@ function finderFor(filter, params) {
}
Discourse.TopicList = Discourse.Model.extend({
canLoadMore: Em.computed.notEmpty("more_topics_url"),
forEachNew: function(topics, callback) {
var topicIds = [];
_.each(this.get('topics'),function(topic) {
@ -68,7 +70,6 @@ Discourse.TopicList = Discourse.Model.extend({
var moreUrl = this.get('more_topics_url');
if (moreUrl) {
var self = this;
this.set('loadingMore', true);
@ -84,7 +85,11 @@ Discourse.TopicList = Discourse.Model.extend({
topics.pushObject(t);
});
self.setProperties({ loadingMore: false, more_topics_url: result.topic_list.more_topics_url });
self.setProperties({
loadingMore: false,
more_topics_url: result.topic_list.more_topics_url
});
Discourse.Session.currentProp('topicList', self);
return self.get('more_topics_url');
}

View File

@ -13,6 +13,13 @@ export default Discourse.Route.extend({
}
},
afterModel: function(model) {
var self = this;
return Discourse.UserBadge.findByBadgeId(model.get('id')).then(function(userBadges) {
self.userBadges = userBadges;
});
},
titleToken: function() {
var model = this.modelFor('badges.show');
if (model) {
@ -21,10 +28,7 @@ export default Discourse.Route.extend({
},
setupController: function(controller, model) {
Discourse.UserBadge.findByBadgeId(model.get('id')).then(function(userBadges) {
controller.set('userBadges', userBadges);
controller.set('userBadgesLoaded', true);
});
controller.set('model', model);
controller.set('userBadges', this.userBadges);
}
});

View File

@ -40,14 +40,12 @@ export default function(filter, extras) {
},
setupController: function(controller, model, trans) {
if (trans) {
controller.setProperties(Em.getProperties(trans, _.keys(queryParams).map(function(v){
return 'queryParams.' + v;
})));
}
var periods = this.controllerFor('discovery').get('periods'),
periodId = model.get('for_period') || (filter.indexOf('/') > 0 ? filter.split('/')[1] : '');

View File

@ -24,17 +24,13 @@ Discourse.DiscoveryRoute = Discourse.Route.extend(Discourse.ScrollTop, Discourse
// If we're already loading don't do anything
if (controller.get('loading')) { return; }
controller.set('loading', true);
controller.set('scheduledSpinner', Ember.run.later(controller, function() {
this.set('loadingSpinner', true);
},500));
return true;
},
loadingComplete: function() {
var controller = this.controllerFor('discovery');
Ember.run.cancel(controller.get('scheduledSpinner'));
controller.setProperties({ loading: false, loadingSpinner: false });
controller.set('loading', false);
if (!Discourse.Session.currentProp('topicListScrollPosition')) {
this._scrollTop();
}

View File

@ -0,0 +1 @@
export default Ember.Route.extend();

View File

@ -4,7 +4,7 @@ export default Discourse.RestrictedUserRoute.extend({
},
renderTemplate: function() {
this.render({ into: 'user', outlet: 'userOutlet' });
this.render({ into: 'user' });
},
setupController: function(controller, model) {
@ -14,7 +14,7 @@ export default Discourse.RestrictedUserRoute.extend({
// A bit odd, but if we leave to /preferences we need to re-render that outlet
deactivate: function() {
this._super();
this.render('preferences', { into: 'user', outlet: 'userOutlet', controller: 'preferences' });
this.render('preferences', { into: 'user', controller: 'preferences' });
},
actions: {

View File

@ -4,13 +4,13 @@ export default Discourse.RestrictedUserRoute.extend({
},
renderTemplate: function() {
return this.render('user/badge-title', { into: 'user', outlet: 'userOutlet' });
return this.render('user/badge-title', { into: 'user' });
},
// A bit odd, but if we leave to /preferences we need to re-render that outlet
deactivate: function() {
this._super();
this.render('preferences', { into: 'user', outlet: 'userOutlet', controller: 'preferences' });
this.render('preferences', { into: 'user', controller: 'preferences' });
},
setupController: function(controller, model) {

View File

@ -4,13 +4,13 @@ export default Discourse.RestrictedUserRoute.extend({
},
renderTemplate: function() {
return this.render({ into: 'user', outlet: 'userOutlet' });
return this.render({ into: 'user' });
},
// A bit odd, but if we leave to /preferences we need to re-render that outlet
deactivate: function() {
this._super();
this.render('preferences', { into: 'user', outlet: 'userOutlet', controller: 'preferences' });
this.render('preferences', { into: 'user', controller: 'preferences' });
},
setupController: function(controller, model) {

View File

@ -4,7 +4,7 @@ export default Discourse.RestrictedUserRoute.extend({
},
renderTemplate: function() {
this.render({ into: 'user', outlet: 'userOutlet' });
this.render({ into: 'user' });
},
setupController: function(controller, model) {
@ -14,7 +14,7 @@ export default Discourse.RestrictedUserRoute.extend({
// A bit odd, but if we leave to /preferences we need to re-render that outlet
deactivate: function() {
this._super();
this.render('preferences', { into: 'user', outlet: 'userOutlet', controller: 'preferences' });
this.render('preferences', { into: 'user', controller: 'preferences' });
}
});

View File

@ -1,5 +1,5 @@
export default Discourse.RestrictedUserRoute.extend({
renderTemplate: function() {
this.render('preferences', { into: 'user', outlet: 'userOutlet', controller: 'preferences' });
this.render('preferences', { into: 'user', controller: 'preferences' });
}
});

View File

@ -4,13 +4,13 @@ export default Discourse.RestrictedUserRoute.extend({
},
renderTemplate: function() {
return this.render({ into: 'user', outlet: 'userOutlet' });
return this.render({ into: 'user' });
},
// A bit odd, but if we leave to /preferences we need to re-render that outlet
deactivate: function() {
this._super();
this.render('preferences', { into: 'user', outlet: 'userOutlet', controller: 'preferences' });
this.render('preferences', { into: 'user', controller: 'preferences' });
},
setupController: function(controller, user) {

View File

@ -4,7 +4,11 @@ export default Discourse.Route.extend({
},
setupController: function(controller, model) {
this.controllerFor('user').set('indexStream', false);
this.controllerFor('user').setProperties({
indexStream: false,
datasource: "badges",
});
if (this.controllerFor('user_activity').get('content')) {
this.controllerFor('user_activity').set('userActionType', -1);
}
@ -12,6 +16,6 @@ export default Discourse.Route.extend({
},
renderTemplate: function() {
this.render('user/badges', {into: 'user', outlet: 'userOutlet'});
this.render('user/badges', {into: 'user'});
}
});

View File

@ -1,6 +1,7 @@
export default Discourse.Route.extend({
beforeModel: function() {
this.controllerFor('user').set('indexStream', true);
return this.replaceWith('userActivity');
}

View File

@ -1,6 +1,6 @@
export default Discourse.Route.extend({
renderTemplate: function() {
this.render({ into: 'user', outlet: 'userOutlet' });
this.render({ into: 'user' });
},
model: function() {

View File

@ -5,15 +5,20 @@ export default Discourse.Route.extend({
},
setupController: function(controller, model) {
this.controllerFor('user').set('indexStream', false);
this.controllerFor('user').setProperties({
indexStream: false,
datasource: "notifications"
});
if (this.controllerFor('user_activity').get('content')) {
this.controllerFor('user_activity').set('userActionType', -1);
}
controller.set('model', model);
controller.set('user', this.modelFor('user'));
},
renderTemplate: function() {
this.render('user-notification-history', {into: 'user', outlet: 'userOutlet'});
this.render('user-notification-history', {into: 'user'});
}
});

View File

@ -1,12 +1,4 @@
/**
The base route for showing an activity stream.
@class UserActivityStreamRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.UserActivityStreamRoute = Discourse.Route.extend({
var UserActivityStreamRoute = Discourse.Route.extend({
model: function() {
return this.modelFor('user').get('stream');
},
@ -16,14 +8,17 @@ Discourse.UserActivityStreamRoute = Discourse.Route.extend({
},
renderTemplate: function() {
this.render('user_stream', {into: 'user', outlet: 'userOutlet'});
this.render('user_stream');
},
setupController: function(controller, model) {
controller.set('model', model);
this.controllerFor('user_activity').set('userActionType', this.get('userActionType'));
this.controllerFor('user').set('indexStream', !this.get('userActionType'));
this.controllerFor('user').setProperties({
indexStream: !this.get('userActionType'),
datasource: "stream"
});
},
actions: {
@ -48,7 +43,7 @@ Discourse.UserActivityStreamRoute = Discourse.Route.extend({
// Build all activity stream routes
['bookmarks', 'edits', 'likes_given', 'likes_received', 'replies', 'posts', 'index'].forEach(function (userAction) {
Discourse["UserActivity" + userAction.classify() + "Route"] = Discourse.UserActivityStreamRoute.extend({
Discourse["UserActivity" + userAction.classify() + "Route"] = UserActivityStreamRoute.extend({
userActionType: Discourse.UserAction.TYPES[userAction]
});
});

View File

@ -14,7 +14,7 @@ function createAdminPostRoute (filter) {
},
renderTemplate: function() {
this.render("user/posts", { into: "user", outlet: "userOutlet" });
this.render("user/posts", { into: "user" });
}
});
}

View File

@ -1,10 +1,13 @@
Discourse.UserTopicListRoute = Discourse.Route.extend({
renderTemplate: function() {
this.render('user_topics_list', {into: 'user', outlet: 'userOutlet'});
this.render('user_topics_list');
},
setupController: function(controller, model) {
this.controllerFor('user').set('indexStream', false);
this.controllerFor('user').setProperties({
indexStream: false,
datasource: "topic_list"
});
this.controllerFor('user-activity').set('userActionType', this.get('userActionType'));
this.controllerFor('user_topics_list').setProperties({
model: model,
@ -30,7 +33,8 @@ function createPMRoute(viewName, path) {
});
this.controllerFor('user').setProperties({
pmView: viewName,
indexStream: false
indexStream: false,
datasource: "topic_list"
});
}
});

View File

@ -62,3 +62,5 @@
</section>
</div>
{{custom-html "footer"}}

View File

@ -19,3 +19,5 @@
</tbody>
</table>
</div>
{{custom-html "footer"}}

View File

@ -35,12 +35,10 @@
</div>
{{/each}}
</div>
{{#if canLoadMore}}
{{loading-spinner}}
{{/if}}
{{else}}
{{#unless userBadgesLoaded}}
{{loading-spinner}}
{{loading-spinner condition=canLoadMore}}
{{#unless canLoadMore}}
{{custom-html "footer"}}
{{/unless}}
{{/if}}
</div>

View File

@ -1,4 +1,4 @@
{{#if loaded}}
{{#loading-spinner condition=loading}}
{{#if topics}}
<table class="topic-list">
<thead>
@ -17,36 +17,36 @@
</thead>
<tbody>
{{#grouped-each topic in topics}}
{{#each topics}}
<tr {{bind-attr class="archived"}}>
<td class='main-link'>
{{topic-status topic=topic}}
<a class='title' href="{{unbound topic.lastUnreadUrl}}">{{{unbound topic.fancy_title}}}</a>
{{topic-post-badges unread=topic.unread
newPosts=topic.new_posts
unseen=topic.unseen
url=topic.lastUnreadUrl}}
{{topic-status topic=this}}
<a class='title' href="{{unbound lastUnreadUrl}}">{{{unbound fancy_title}}}</a>
{{topic-post-badges unread=unread
newPosts=new_posts
unseen=unseen
url=lastUnreadUrl}}
</td>
{{raw "list/category-column" hideCategory=controller.hideCategory category=topic.category}}
{{raw "list/category-column" hideCategory=controller.hideCategory category=category}}
{{posts-count-column topic=topic class="num" action="clickedPosts"}}
{{posts-count-column topic=this class="num" action="clickedPosts"}}
{{#if controller.showParticipants}}
<td class='participants'>
{{#each topic.participants}}
<a href="{{user.path}}" class="{{extras}}">{{avatar this usernamePath="user.username" imageSize="small"}}</a>
{{#each participants}}
<a href="{{unbound user.path}}" data-user-card="{{unbound user.username}}" class="{{unbound extras}}">{{avatar this usernamePath="user.username" imageSize="small"}}</a>
{{/each}}
</td>
{{/if}}
<td {{bind-attr class=":num :views topic.viewsHeat"}}>
{{number topic.views numberKey="views_long"}}
<td {{bind-attr class=":num :views viewsHeat"}}>
{{number views numberKey="views_long"}}
</td>
{{raw "list/activity-column" topic=topic class="num" tagName="td"}}
{{raw "list/activity-column" topic=this class="num" tagName="td"}}
</tr>
{{/grouped-each}}
{{/each}}
</tbody>
</table>
@ -55,6 +55,4 @@
{{i18n choose_topic.none_found}}
</div>
{{/if}}
{{else}}
{{loading-spinner}}
{{/if}}
{{/loading-spinner}}

View File

@ -0,0 +1,5 @@
{{#if condition}}
<div {{bind-attr class=":spinner size"}}></div>
{{else}}
{{yield}}
{{/if}}

View File

@ -33,20 +33,26 @@
<dd>{{location.phone}}</dd>
{{/if}}
{{else}}
<div class="spinner"></div>
{{loading-spinner size="small"}}
{{/if}}
<dt>{{i18n ip_lookup.other_accounts}}</dt>
<dd>
{{#if other_accounts_loading}}
<div class="spinner"></div>
{{else}}
{{#each other_accounts}}
{{#link-to "adminUser" this}}{{avatar this usernamePath="user.username" imageSize="small"}}{{/link-to}}
<dt>{{i18n ip_lookup.other_accounts}}&nbsp;<strong>{{other_accounts.length}}</strong></dt>
<dd class="other-accounts">
{{#loading-spinner size="small" condition=otherAccountsLoading}}
{{#if other_accounts}}
<ul>
{{#each other_accounts}}
<li>
{{#link-to "adminUser" this}}{{avatar this usernamePath="user.username" imageSize="small"}}&nbsp;{{username}}{{/link-to}}
({{trustLevel.name}}),
<strong>{{i18n ip_lookup.read_time}}</strong>&nbsp;{{time_read}},
<strong>{{i18n ip_lookup.topics_entered}}</strong>&nbsp;{{topics_entered}}
</li>
{{/each}}
{{else}}
{{i18n ip_lookup.no_other_accounts}}
{{/each}}
{{/if}}
{{/if}}
{{/loading-spinner}}
<dd>
</dl>
<button class="btn close" {{action "hide"}}>{{i18n close}}</button>

View File

@ -1,11 +1,11 @@
<h3><i class='fa fa-envelope'></i> {{i18n private_message_info.title}}</h3>
<div class='participants clearfix'>
{{#grouped-each details.allowed_groups}}
{{#each details.allowed_groups}}
<div class='user group'>
#{{unbound name}}
</div>
{{/grouped-each}}
{{#grouped-each details.allowed_users}}
{{/each}}
{{#each details.allowed_users}}
<div class='user'>
{{#link-to 'user' this}}
{{avatar this imageSize="small"}}
@ -17,7 +17,7 @@
<a class='remove-invited' {{action "removeAllowedUser" this}}><i class="fa fa-times"></i></a>
{{/if}}
</div>
{{/grouped-each}}
{{/each}}
</div>
{{#if details.can_invite_to}}
<div class='controls'>

View File

@ -46,7 +46,9 @@
{{/if}}
{{#if showPosterAvatar}}
<li {{bind-attr class=":avatars mapCollapsed::hidden"}}>
{{#grouped-each participant in details.fewParticipants}}{{topic-participant participant=participant}}{{/grouped-each}}
{{#each details.fewParticipants}}
{{topic-participant participant=this}}
{{/each}}
</li>
{{/if}}
</ul>
@ -54,14 +56,16 @@
{{#unless mapCollapsed}}
<section class='avatars clearfix'>
{{#grouped-each participant in details.participants}}{{topic-participant participant=participant}}{{/grouped-each}}
{{#each details.participants}}
{{topic-participant participant=this}}
{{/each}}
</section>
{{#if infoLinks}}
<section class='links'>
<table class='topic-links'>
{{#grouped-each infoLinks}}
{{#each infoLinks}}
<tr>
<td>
<span class='badge badge-notification clicks' title='{{i18n topic_map.clicks count=clicks}}'>{{clicks}}</span>
@ -73,7 +77,7 @@
{{link-domain this}}
</td>
</tr>
{{/grouped-each}}
{{/each}}
</table>
{{#if showAllLinksControls}}

View File

@ -1,4 +1,6 @@
{{loading-spinner}}
<div class='composer-loading'>
{{loading-spinner}}
</div>
<div class='contents'>
@ -77,7 +79,7 @@
<div class="composer-bottom-right">
<a href="#" {{action "togglePreview"}} class='toggle-preview'>{{{model.toggleText}}}</a>
<div id="file-uploading" {{bind-attr class="view.isUploading::hidden"}}>
<i class='fa fa-spinner fa-spin'></i> {{i18n upload_selector.uploading}} {{view.uploadProgress}}% <a id="cancel-file-upload">{{i18n cancel}}</a>
{{loading-spinner size="small"}} {{i18n upload_selector.uploading}} {{view.uploadProgress}}% <a id="cancel-file-upload">{{i18n cancel}}</a>
</div>
{{#if site.mobileView}}
<a {{bind-attr class=":mobile-file-upload view.isUploading:hidden"}}>{{i18n upload}}</a>

View File

@ -10,12 +10,9 @@
</div>
</div>
<div {{bind-attr class="loadingSpinner::hidden"}}>
{{loading-spinner}}
</div>
{{loading-spinner condition=loading}}
<div {{bind-attr class=":container :list-container loadingSpinner:hidden"}}>
<div {{bind-attr class=":container :list-container loading:hidden"}}>
<div class="row">
<div class="full-width">
<div id='header-list-area'>
@ -23,7 +20,6 @@
</div>
</div>
</div>
<div class="row">
<div class="full-width">
<div id='list-area'>
@ -32,3 +28,6 @@
</div>
</div>
</div>
{{#if loadedAllItems}}
{{custom-html "footer"}}
{{/if}}

View File

@ -71,9 +71,7 @@
</div>
<footer class='topic-list-bottom'>
{{#if loadingMore}}
{{loading-spinner}}
{{/if}}
{{loading-spinner condition=loadingMore}}
{{#if allLoaded}}
{{#if showDismissRead}}
<button title="{{i18n topics.bulk.dismiss_topics_tooltip}}" id='dismiss-topics' class='btn dismiss-read' {{action "dismissRead" "topics"}}>{{i18n topics.bulk.dismiss_topics}}</button>

View File

@ -16,9 +16,7 @@
{{#each buttonData in enabledButtons}}
<button class="btn {{unbound buttonData.classes}}" {{action buttonData.action}}>{{boundI18n buttonData.key}}</button>
{{/each}}
{{#if loading}}
<i class="fa fa-spin fa-spinner"></i>
{{/if}}
{{loading-spinner condition=loading}}
</div>
</div>
</div>

View File

@ -4,23 +4,25 @@
<li {{bind-attr class="showingIndex:active"}}>
{{#link-to 'group.index' model}}{{i18n groups.posts}}
<span class='count'>({{counts.posts}})</span>
<span class='fa fa-chevron-right'></span>{{/link-to}}
{{/link-to}}
</li>
<li {{bind-attr class="showingMembers:active"}}>
{{#link-to 'group.members' model}}{{i18n groups.members}}
<span class='count'>({{counts.members}})</span>
<span class='fa fa-chevron-right'></span>{{/link-to}}
{{/link-to}}
</li>
</ul>
</section>
<section class='user-main'>
<section class='about group'>
<div class='details'>
<h1>{{name}}</h1>
</div>
</section>
{{outlet}}
<section class='user-right groups'>
<section class='about group'>
<div class='details'>
<h1>{{name}}</h1>
</div>
</section>
{{outlet}}
</section>
</section>
</div>

View File

@ -7,6 +7,7 @@
<span class="title">
<a href="{{unbound url}}">{{unbound title}}</a>
</span>
<span class="category">{{category-link category}}</span>
{{#if byName}}
<span class="name">
{{unbound byName}}

View File

@ -1,16 +1,17 @@
{{#if loaded}}
{{#loading-spinner condition=loading}}
{{#if topics}}
<table class="topic-list">
{{#grouped-each topic in topics}}
<tbody>
{{#each topics}}
<tr {{bind-attr class="archived"}}>
<td>
<div class='main-link clearfix'>
{{topic-status topic=this}}
{{topic-link this}}
{{topic-post-badges unread=unread
newPosts=topic.new_posts
unseen=topic.unseen
url=topic.lastUnreadUrl}}
newPosts=new_posts
unseen=unseen
url=lastUnreadUrl}}
{{#if hasExcerpt}}
<div class="topic-excerpt">
@ -26,8 +27,8 @@
</div>
<div class="topic-item-stats clearfix">
<div class="pull-right">
{{posts-count-column topic=topic tagName="div" class="num posts" action="clickedPosts"}}
{{raw "list/activity-column" topic=topic tagName="div" class="num activity last"}}
{{posts-count-column topic=this tagName="div" class="num posts" action="clickedPosts"}}
{{raw "list/activity-column" topic=this tagName="div" class="num activity last"}}
</div>
{{#unless controller.hideCategory}}
<div class='category'>
@ -36,8 +37,8 @@
{{/unless}}
{{#if controller.showParticipants}}
<div class='participants'>
{{#each topic.participants}}
<a href="{{user.path}}" class="{{extras}}">{{avatar this usernamePath="user.username" imageSize="small"}}</a>
{{#each participants}}
<a href="{{unbound user.path}}" class="{{unbound extras}}">{{avatar this usernamePath="user.username" imageSize="small"}}</a>
{{/each}}
</div>
{{/if}}
@ -45,13 +46,11 @@
</div>
</td>
</tr>
{{/grouped-each}}
</table>
{{else}}
<div class='alert alert-info'>
{{i18n choose_topic.none_found}}
{{/each}}
</table>
{{else}}
<div class='alert alert-info'>
{{i18n choose_topic.none_found}}
</div>
{{/if}}
{{else}}
{{loading-spinner}}
{{/if}}
{{/loading-spinner}}

View File

@ -24,9 +24,7 @@
</div>
<footer class='topic-list-bottom'>
{{#if loadingMore}}
{{loading-spinner}}
{{/if}}
{{loading-spinner condition=loadingMore}}
{{#if allLoaded}}
{{#if showDismissRead}}
<button title="{{i18n topics.bulk.dismiss_topics_tooltip}}" id='dismiss-topics' class='btn dismiss-read' {{action "dismissRead" "topics"}}>{{i18n topics.bulk.dismiss_topics}}</button>

View File

@ -88,13 +88,11 @@
{{#if showCreateForm}}
<div class="modal-footer">
<button class='btn btn-large btn-primary' {{bind-attr disabled="submitDisabled"}} {{action "createAccount"}}>{{i18n create_account.title}}</button>
{{#if formSubmitted}}
&nbsp; <i class='fa fa-spinner fa-spin'></i>
{{else}}
{{#loading-spinner condition=formSubmitted size="small"}}
<button class="btn btn-large" id="login-link" {{action "showLogin"}}>
{{i18n log_in}}
</button>
{{/if}}
{{/loading-spinner}}
</div>
{{/if}}
{{/unless}}

View File

@ -30,4 +30,5 @@
{{#if canDeleteSpammer}}
<button class="btn btn-danger" {{action "deleteSpammer" userDetails}} {{bind-attr disabled="submitDisabled"}} title="{{i18n flagging.delete_spammer}}"><i class="fa fa-exclamation-triangle"></i> {{i18n flagging.delete_spammer}}</button>
{{/if}}
<span class="hint">{{{i18n flagging.private_reminder}}}</span>
</div>

View File

@ -4,11 +4,9 @@
<button title="{{i18n post.revisions.controls.first}}" {{bind-attr class=":btn :standard :no-text displayGoToFirst::invisible" disabled=loading}} {{action "loadFirstVersion"}}><i class="fa fa-fast-backward"></i></button>
<button title="{{i18n post.revisions.controls.previous}}" {{bind-attr class=":btn :standard :no-text displayGoToPrevious::invisible" disabled=loading}} {{action "loadPreviousVersion"}}><i class="fa fa-backward"></i></button>
<div id="revision-numbers" {{bind-attr class="displayRevisions::invisible"}}>
{{#if loading}}
<div id='revision-loading'><i class='fa fa-spinner fa-spin'></i>{{i18n loading}}</div>
{{else}}
{{#loading-spinner condition=loading size="small"}}
{{boundI18n revisionsTextKey previousBinding="previousVersion" currentBinding="current_version" totalBinding="version_count"}}
{{/if}}
{{/loading-spinner}}
</div>
<button title="{{i18n post.revisions.controls.next}}" {{bind-attr class=":btn :standard :no-text displayGoToNext::invisible" disabled=loading}} {{action "loadNextVersion"}}><i class="fa fa-forward"></i></button>
<button title="{{i18n post.revisions.controls.last}}" {{bind-attr class=":btn :standard :no-text displayGoToLast::invisible" disabled=loading}} {{action "loadLastVersion"}}><i class="fa fa-fast-forward"></i></button>

View File

@ -38,6 +38,7 @@
<h4>{{i18n keyboard_shortcuts_help.actions.title}}</h4>
<ul>
<li>{{{i18n keyboard_shortcuts_help.actions.star}}}</li>
<li>{{{i18n keyboard_shortcuts_help.actions.pin_unpin_topic}}}</li>
<li>{{{i18n keyboard_shortcuts_help.actions.share_topic}}}</li>
<li>{{{i18n keyboard_shortcuts_help.actions.share_post}}}</li>
<li>{{{i18n keyboard_shortcuts_help.actions.reply_as_new_topic}}}</li>

View File

@ -56,7 +56,5 @@
&nbsp; {{i18n login.authenticating}}
{{/if}}
{{#if showSpinner}}
&nbsp; <i class='fa fa-spinner fa-spin'></i>
{{/if}}
{{loading-spinner condition=showSpinner size="small"}}
</div>

View File

@ -1,5 +1,5 @@
<section class="d-dropdown" id="notifications-dropdown">
{{#unless loadingNotifications}}
{{#loading-spinner condition=loadingNotifications}}
{{#if content}}
<ul>
{{#each itemController="notification"}}
@ -12,7 +12,5 @@
{{else}}
<div class="none">{{i18n notifications.none}}</div>
{{/if}}
{{else}}
<div class='loading'><i class='fa fa-spinner fa-spin'></i></div>
{{/unless}}
{{/loading-spinner}}
</section>

View File

@ -50,16 +50,16 @@
</div>
{{/if}}
{{#if wiki}}
<div class="post-info wiki" title="{{i18n post.wiki.about}}" {{action "editPost" this}}><i class="fa fa-pencil-square-o"></i></div>
<div class="post-info wiki" title="{{i18n post.wiki.about}}" {{action "editPost" this}}>{{fa-icon "pencil-square-o"}}</div>
{{/if}}
{{#if via_email}}
{{#if canViewRawEmail}}
<div class="post-info via-email raw-email" title="{{i18n post.via_email}}" {{action "showRawEmail" this}}><i class="fa fa-envelope-o"></i></div>
<div class="post-info via-email raw-email" title="{{i18n post.via_email}}" {{action "showRawEmail" this}}>{{fa-icon "envelope-o"}}</div>
{{else}}
<div class="post-info via-email" title="{{i18n post.via_email}}"><i class="fa fa-envelope-o"></i></div>
<div class="post-info via-email" title="{{i18n post.via_email}}">{{fa-icon "envelope-o"}}</div>
{{/if}}
{{/if}}
<div {{bind-attr class=":read-state read"}} title="{{i18n post.unread}}"><i class='fa fa-circle'></i></div>
<div {{bind-attr class=":read-state read"}} title="{{i18n post.unread}}">{{fa-icon "circle"}}</div>
</div>
<div {{bind-attr class=":select-posts controller.multiSelect::hidden"}}>
@ -84,7 +84,15 @@
{{/if}}
{{view 'post-menu' post=this adminMenu=view.adminMenu}}
</div>
{{view 'replies' content=replies}}
{{#if replies}}
<section class='embedded-posts bottom'>
{{#each reply in replies}}
{{view 'embedded-post' content=reply}}
{{/each}}
</section>
{{/if}}
{{discourse-action-history post=this}}
{{view 'topic-map-container' post=this topic=controller.model}}
</div>

View File

@ -7,3 +7,5 @@
{{/if}}
</div>
</div>
{{custom-html "footer"}}

View File

@ -5,7 +5,6 @@
</div>
{{#if postStream.loaded}}
{{#if postStream.firstPostPresent}}
<div id='topic-title'>
<div class='container'>
@ -43,7 +42,6 @@
</a>
{{/if}}
{{#if details.can_edit}}
<a href='#' {{action "editTopic"}} class='edit-topic' title='{{i18n edit}}'>{{fa-icon pencil}}</a>
{{/if}}
@ -72,9 +70,7 @@
{{render 'topic-progress'}}
{{#if postStream.loadingAbove}}
{{loading-spinner}}
{{/if}}
{{loading-spinner condition=postStream.loadingAbove}}
{{#unless postStream.loadingFilter}}
{{cloaked-collection cloakView="post"
@ -89,15 +85,11 @@
offsetFixedBottom="#reply-control"}}
{{/unless}}
{{#if postStream.loadingBelow}}
{{loading-spinner}}
{{/if}}
{{loading-spinner condition=postStream.loadingBelow}}
</div>
<div id='topic-bottom'></div>
{{#if postStream.loadingFilter}}
<div class='spinner small'></div>
{{else}}
{{#loading-spinner condition=postStream.loadingFilter}}
{{#if postStream.loadedAllPosts}}
{{view 'topic-closing' topic=model}}
@ -112,40 +104,38 @@
<h3>{{{view.browseMoreMessage}}}</h3>
</div>
{{/if}}
{{/if}}
{{/if}}
{{/loading-spinner}}
</section>
</div>
</div>
{{else}}
{{#if hasError}}
<div class='container'>
{{#if notFoundHtml}}
{{{notFoundHtml}}}
{{else}}
<div class="topic-error">
<div>{{message}}</div>
{{#if noRetry}}
{{#unless currentUser}}
<button {{action "showLogin"}} class='btn btn-primary topic-retry'>{{fa-icon user}} {{i18n log_in}}</button>
{{/unless}}
{{else}}
<button class="btn btn-primary topic-retry" {{action "retryLoading"}}><i class="fa fa-refresh"></i> {{i18n errors.buttons.again}}</button>
{{/if}}
</div>
{{#if retrying}}
{{loading-spinner}}
<div class='container'>
{{#loading-spinner condition=noErrorYet}}
{{#if notFoundHtml}}
{{{notFoundHtml}}}
{{else}}
<div class="topic-error">
<div>{{message}}</div>
{{#if noRetry}}
{{#unless currentUser}}
<button {{action "showLogin"}} class='btn btn-primary topic-retry'>{{fa-icon user}} {{i18n log_in}}</button>
{{/unless}}
{{else}}
<button class="btn btn-primary topic-retry" {{action "retryLoading"}}>{{fa-icon "refresh"}} {{i18n errors.buttons.again}}</button>
{{/if}}
</div>
{{loading-spinner condition=retrying}}
{{/if}}
{{/if}}
</div>
{{else}}
<div class='container'>
{{loading-spinner}}
</div>
{{/if}}
{{/loading-spinner}}
</div>
{{/if}}
{{#if postStream.loadedAllPosts}}
{{custom-html "footer"}}
{{/if}}
{{render "share"}}

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