mirror of
https://github.com/discourse/discourse.git
synced 2024-11-23 10:30:01 +08:00
Merge branch 'master' into vagrant-chef
This commit is contained in:
commit
d2cebda786
|
@ -7,13 +7,11 @@ on Discourse with:
|
|||
|
||||
### Getting Started
|
||||
|
||||
1. Install the Xcode tools: https://developer.apple.com/xcode/
|
||||
2. Install VirtualBox: https://www.virtualbox.org/wiki/Downloads
|
||||
3. Install Ruby 1.9.3. We recommend RVM: https://rvm.io/
|
||||
4. Open a terminal
|
||||
5. Clone the project: `git@github.com:discourse/discourse.git`
|
||||
6. Enter the project directory: `cd discourse`
|
||||
7. Install vagrant: `gem install vagrant`
|
||||
1. Install VirtualBox: https://www.virtualbox.org/wiki/Downloads
|
||||
2. Install Vagrant: https://www.vagrantup.com/
|
||||
3. Open a terminal
|
||||
4. Clone the project: `git@github.com:discourse/discourse.git`
|
||||
5. Enter the project directory: `cd discourse`
|
||||
|
||||
### Using Vagrant
|
||||
|
||||
|
@ -22,7 +20,7 @@ When you're ready to start working, boot the VM:
|
|||
vagrant up
|
||||
```
|
||||
|
||||
It should prompt you for your admin password. This is so it can mount your local files inside the VM for an easy workflow.
|
||||
On Windows, it will prompt you for your admin password. This is so it can mount your local files inside the VM for an easy workflow.
|
||||
|
||||
(The first time you do this, it will take a while as it downloads the VM image and installs it. Go grab a coffee.)
|
||||
|
||||
|
|
9
Gemfile
9
Gemfile
|
@ -6,7 +6,6 @@ gem 'hiredis'
|
|||
gem 'em-redis'
|
||||
gem 'rails'
|
||||
gem 'pg'
|
||||
gem 'haml'
|
||||
gem 'sass'
|
||||
gem 'rake'
|
||||
# errbit is broken with 3.1.3 for now
|
||||
|
@ -20,8 +19,6 @@ gem 'nokogiri'
|
|||
gem 'seed-fu'
|
||||
gem 'sanitize'
|
||||
|
||||
|
||||
gem 'slim', '<= 1.3.0'
|
||||
gem 'sinatra', :require => nil
|
||||
gem 'clockwork', :require => false
|
||||
|
||||
|
@ -70,10 +67,9 @@ group :assets do
|
|||
gem 'uglifier'
|
||||
# gem "asset_sync"
|
||||
gem 'turbo-sprockets-rails3'
|
||||
end
|
||||
|
||||
# need this to compile coffee on the fly
|
||||
gem 'coffee-script'
|
||||
end
|
||||
|
||||
gem 'hpricot'
|
||||
gem 'jquery-rails'
|
||||
|
@ -97,12 +93,11 @@ group :test, :development do
|
|||
gem 'guard-rspec'
|
||||
gem 'guard-spork'
|
||||
gem 'mocha', :require => false
|
||||
gem 'test-unit', :require => "test/unit"
|
||||
gem 'simplecov', :require => false
|
||||
gem 'image_optim'
|
||||
gem 'certified'
|
||||
gem 'rb-fsevent'
|
||||
gem 'rb-inotify', :require => RUBY_PLATFORM.include?('linux') && 'rb-inotify'
|
||||
gem 'rb-inotify', '~> 0.8.8', :require => RUBY_PLATFORM.include?('linux') && 'rb-inotify'
|
||||
gem 'terminal-notifier-guard', :require => RUBY_PLATFORM.include?('darwin') && 'terminal-notifier-guard'
|
||||
end
|
||||
|
||||
|
|
|
@ -256,7 +256,7 @@ GEM
|
|||
thor (>= 0.14.6, < 2.0)
|
||||
rake (10.0.3)
|
||||
rb-fsevent (0.9.3)
|
||||
rb-inotify (0.9.0)
|
||||
rb-inotify (0.8.8)
|
||||
ffi (>= 0.5.0)
|
||||
rdoc (3.12)
|
||||
json (~> 1.4)
|
||||
|
@ -416,7 +416,7 @@ DEPENDENCIES
|
|||
rails_multisite!
|
||||
rake
|
||||
rb-fsevent
|
||||
rb-inotify
|
||||
rb-inotify (~> 0.8.8)
|
||||
redis
|
||||
redis-rails
|
||||
rest-client
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
guard 'spork' do
|
||||
guard :spork, wait: 120 do
|
||||
watch('config/application.rb')
|
||||
watch('config/environment.rb')
|
||||
watch(%r{^config/environments/.*\.rb$})
|
||||
|
@ -27,7 +27,7 @@ guard 'jasmine', jasmine_options do watch(%r{spec/javascripts/spec\.(js\.coffee|
|
|||
watch(%r{app/assets/javascripts/(.+?)\.(js\.coffee|js|coffee)$}) { "spec/javascripts" }
|
||||
end
|
||||
|
||||
guard 'rspec', :focus_on_failed => true, :version => 2, :cli => "--drb" do
|
||||
guard 'rspec', :focus_on_failed => true, :cli => "--drb" do
|
||||
watch(%r{^spec/.+_spec\.rb$})
|
||||
#watch(%r{^lib/jobs/(.+)\.rb$}) { |m| "spec/components/jobs/#{m[1]}_spec.rb" }
|
||||
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/components/#{m[1]}_spec.rb" }
|
||||
|
|
|
@ -20,6 +20,7 @@ If you're interested in helping us develop Discourse, please start with our **[D
|
|||
```
|
||||
git clone git@github.com:discourse/discourse.git
|
||||
cd discourse
|
||||
bundle install
|
||||
rake db:create
|
||||
rake db:migrate
|
||||
rake db:seed_fu
|
||||
|
|
21
Vagrantfile
vendored
21
Vagrantfile
vendored
|
@ -1,14 +1,29 @@
|
|||
# -*- mode: ruby -*-
|
||||
# vi: set ft=ruby :
|
||||
# See https://github.com/discourse/core/blob/master/DEVELOPMENT.md
|
||||
#
|
||||
Vagrant::Config.run do |config|
|
||||
config.vm.box = 'discourse-pre'
|
||||
config.vm.box_url = 'http://www.discourse.org/vms/discourse-pre.box'
|
||||
|
||||
# Make this VM reachable on the host network as well, so that other
|
||||
# VM's running other browsers can access our dev server.
|
||||
config.vm.network :hostonly, '192.168.10.200'
|
||||
|
||||
# Make it so that network access from the vagrant guest is able to
|
||||
# use SSH private keys that are present on the host without copying
|
||||
# them into the VM.
|
||||
config.ssh.forward_agent = true
|
||||
|
||||
# This setting gives the VM 512MB of MEMORIES instead of the default 384.
|
||||
config.vm.customize ["modifyvm", :id, "--memory", 512]
|
||||
|
||||
# This setting makes it so that network access from inside the vagrant guest
|
||||
# is able to resolve DNS using the hosts VPN connection.
|
||||
config.vm.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]
|
||||
|
||||
config.vm.forward_port 3000, 4000
|
||||
config.vm.forward_port 1080, 4080 # Mailcatcher
|
||||
|
||||
if RUBY_PLATFORM =~ /darwin/
|
||||
config.vm.share_folder("v-root", "/vagrant", ".", :nfs => true)
|
||||
end
|
||||
config.vm.share_folder("v-root", "/vagrant", ".")
|
||||
end
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
window.Discourse.AdminUser = Discourse.Model.extend
|
||||
|
||||
deleteAllPosts: ->
|
||||
@set('can_delete_all_posts', false)
|
||||
$.ajax "/admin/users/#{@get('id')}/delete_all_posts", type: 'PUT'
|
||||
|
||||
# Revoke the user's admin access
|
||||
revokeAdmin: ->
|
||||
@set('admin',false)
|
||||
|
@ -18,8 +22,6 @@ window.Discourse.AdminUser = Discourse.Model.extend
|
|||
type: 'POST'
|
||||
bootbox.alert("Message sent to all clients!")
|
||||
|
||||
|
||||
|
||||
approve: ->
|
||||
@set('can_approve', false)
|
||||
@set('approved', true)
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
<tr>
|
||||
<td></td>
|
||||
<td class='message'>
|
||||
<div>{{avatar user imageSize="small"}} {{message}}</div>
|
||||
<div><a href="/admin{{unbound user.path}}">{{avatar user imageSize="small"}}</a> {{message}}</div>
|
||||
</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
|
|
|
@ -139,6 +139,14 @@
|
|||
<div class='display-row'>
|
||||
<div class='field'>{{i18n admin.user.post_count}}</div>
|
||||
<div class='value'>{{content.post_count}}</div>
|
||||
<div class='controls'>
|
||||
{{#if content.can_delete_all_posts}}
|
||||
<button class='btn' {{action deleteAllPosts target="content"}}>
|
||||
<i class='icon icon-trash'></i>
|
||||
{{i18n admin.user.delete_all_posts}}
|
||||
</button>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n admin.user.posts_read_count}}</div>
|
||||
|
|
|
@ -19,6 +19,11 @@ class Admin::UsersController < Admin::AdminController
|
|||
render_serialized(@user, AdminDetailedUserSerializer, root: false)
|
||||
end
|
||||
|
||||
def delete_all_posts
|
||||
@user = User.where(id: params[:user_id]).first
|
||||
@user.delete_all_posts!(guardian)
|
||||
render nothing: true
|
||||
end
|
||||
def ban
|
||||
@user = User.where(id: params[:user_id]).first
|
||||
guardian.ensure_can_ban!(@user)
|
||||
|
|
|
@ -319,11 +319,11 @@ class Post < ActiveRecord::Base
|
|||
Post.transaction do
|
||||
self.last_version_at = revised_at
|
||||
updater.call(true)
|
||||
EditRateLimiter.new(updated_by).performed!
|
||||
EditRateLimiter.new(updated_by).performed! unless opts[:bypass_rate_limiter]
|
||||
|
||||
# If a new version is created of the last post, bump it.
|
||||
unless Post.where('post_number > ? and topic_id = ?', self.post_number, self.topic_id).exists?
|
||||
topic.update_column(:bumped_at, Time.now)
|
||||
topic.update_column(:bumped_at, Time.now) unless opts[:bypass_bump]
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
class SiteCustomization < ActiveRecord::Base
|
||||
|
||||
ENABLED_KEY = '7e202ef2-56d7-47d5-98d8-a9c8d15e57dd'
|
||||
CACHE_PATH = 'stylesheet-cache'
|
||||
@lock = Mutex.new
|
||||
|
||||
|
@ -49,18 +50,46 @@ footer:after{ content: '#{error}' }"
|
|||
self.remove_from_cache!
|
||||
end
|
||||
|
||||
def self.enabled_key
|
||||
ENABLED_KEY.dup << RailsMultisite::ConnectionManagement.current_db
|
||||
end
|
||||
|
||||
def self.enabled_style_key
|
||||
@cache ||= {}
|
||||
preview_style = @cache[self.enabled_key]
|
||||
return nil if preview_style == :none
|
||||
return preview_style if preview_style
|
||||
|
||||
@lock.synchronize do
|
||||
style = self.where(enabled: true).first
|
||||
if style
|
||||
@cache[self.enabled_key] = style.key
|
||||
return style.key
|
||||
else
|
||||
@cache[self.enabled_key] = :none
|
||||
return nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.custom_stylesheet(preview_style)
|
||||
preview_style ||= enabled_style_key
|
||||
style = lookup_style(preview_style)
|
||||
style.stylesheet_link_tag.html_safe if style
|
||||
end
|
||||
|
||||
def self.custom_header(preview_style)
|
||||
preview_style ||= enabled_style_key
|
||||
style = lookup_style(preview_style)
|
||||
style.header.html_safe if style
|
||||
if style && style.header
|
||||
style.header.html_safe
|
||||
else
|
||||
""
|
||||
end
|
||||
end
|
||||
|
||||
def self.override_default_style(preview_style)
|
||||
preview_style ||= enabled_style_key
|
||||
style = lookup_style(preview_style)
|
||||
style.override_default_style if style
|
||||
end
|
||||
|
@ -103,6 +132,7 @@ footer:after{ content: '#{error}' }"
|
|||
end
|
||||
|
||||
def remove_from_cache!
|
||||
self.class.remove_from_cache!(self.class.enabled_key)
|
||||
self.class.remove_from_cache!(self.key)
|
||||
end
|
||||
|
||||
|
|
|
@ -335,6 +335,18 @@ class User < ActiveRecord::Base
|
|||
PrettyText.excerpt(bio_cooked, 350)
|
||||
end
|
||||
|
||||
def delete_all_posts!(guardian)
|
||||
raise Discourse::InvalidAccess unless guardian.can_delete_all_posts? self
|
||||
|
||||
posts.order("post_number desc").each do |p|
|
||||
if p.post_number == 1
|
||||
p.topic.destroy
|
||||
else
|
||||
p.destroy
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def is_banned?
|
||||
!banned_till.nil? && banned_till > DateTime.now
|
||||
end
|
||||
|
|
|
@ -8,7 +8,8 @@ class AdminDetailedUserSerializer < AdminUserSerializer
|
|||
:post_count,
|
||||
:flags_given_count,
|
||||
:flags_received_count,
|
||||
:private_topics_count
|
||||
:private_topics_count,
|
||||
:can_delete_all_posts
|
||||
|
||||
has_one :approved_by, serializer: BasicUserSerializer, embed: :objects
|
||||
|
||||
|
@ -20,6 +21,10 @@ class AdminDetailedUserSerializer < AdminUserSerializer
|
|||
scope.can_grant_admin?(object)
|
||||
end
|
||||
|
||||
def can_delete_all_posts
|
||||
scope.can_delete_all_posts?(object)
|
||||
end
|
||||
|
||||
def moderator
|
||||
object.has_trust_level?(:moderator)
|
||||
end
|
||||
|
|
9
app/views/facebook/complete.html.erb
Normal file
9
app/views/facebook/complete.html.erb
Normal file
|
@ -0,0 +1,9 @@
|
|||
<html>
|
||||
<head></head>
|
||||
<body>
|
||||
<script type="text/javascript">
|
||||
window.opener.Discourse.authenticationComplete(#{@data.to_json});
|
||||
window.close();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -50,12 +50,8 @@
|
|||
</style>
|
||||
|
||||
<%- unless SiteCustomization.override_default_style(session[:preview_style]) %>
|
||||
<%- if params[:shiny] %>
|
||||
<%=stylesheet_link_tag "shiny/shiny"%>
|
||||
<%- else %>
|
||||
<%=stylesheet_link_tag "application"%>
|
||||
<%- end %>
|
||||
<%- end %>
|
||||
|
||||
<%- if mini_profiler_enabled? %>
|
||||
<%- Rack::MiniProfiler.step "stylsheet" do%>
|
||||
|
|
9
app/views/twitter/complete.html.erb
Normal file
9
app/views/twitter/complete.html.erb
Normal file
|
@ -0,0 +1,9 @@
|
|||
<html>
|
||||
<head></head>
|
||||
<body>
|
||||
<script type="text/javascript">
|
||||
window.opener.Discourse.authenticationComplete(#{@data.to_json});
|
||||
window.close();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
9
app/views/user_open_ids/complete.html.erb
Normal file
9
app/views/user_open_ids/complete.html.erb
Normal file
|
@ -0,0 +1,9 @@
|
|||
<html>
|
||||
<head></head>
|
||||
<body>
|
||||
<script type="text/javascript">
|
||||
window.opener.Discourse.authenticationComplete(#{@data.to_json});
|
||||
window.close();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -369,6 +369,7 @@ en:
|
|||
ban_failed: "Something went wrong banning this user {{error}}"
|
||||
unban_failed: "Something went wrong unbanning this user {{error}}"
|
||||
ban_duration: "How long would you like to ban the user for? (days)"
|
||||
delete_all_posts: "Delete all posts"
|
||||
ban: "Ban"
|
||||
unban: "Unban"
|
||||
banned: "Banned?"
|
||||
|
|
|
@ -30,6 +30,7 @@ Discourse::Application.routes.draw do
|
|||
put 'approve-bulk' => 'users#approve_bulk'
|
||||
end
|
||||
put 'ban' => 'users#ban'
|
||||
put 'delete_all_posts' => 'users#delete_all_posts'
|
||||
put 'unban' => 'users#unban'
|
||||
put 'revoke_admin' => 'users#revoke_admin'
|
||||
put 'grant_admin' => 'users#grant_admin'
|
||||
|
|
|
@ -167,6 +167,13 @@ class Guardian
|
|||
@user.id == user_id
|
||||
end
|
||||
|
||||
def can_delete_all_posts?(user)
|
||||
return false unless is_admin?
|
||||
return false if user.created_at < 7.days.ago
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
# Support for ensure_{blah}! methods.
|
||||
def method_missing(method, *args, &block)
|
||||
if method.to_s =~ /^ensure_(.*)\!$/
|
||||
|
|
|
@ -23,6 +23,7 @@ module Slug
|
|||
str.gsub!(/[^a-z0-9 -]/, '')
|
||||
str.gsub!(/\s+/, '-')
|
||||
str.gsub!(/\-+/, '-')
|
||||
str.gsub!(/^-|-$/, '')
|
||||
|
||||
str
|
||||
end
|
||||
|
|
|
@ -27,6 +27,13 @@ describe Slug do
|
|||
Slug.for("a....b.....c").should == "a-b-c"
|
||||
end
|
||||
|
||||
it 'strips trailing punctuation' do
|
||||
Slug.for("hello...").should == "hello"
|
||||
end
|
||||
|
||||
it 'strips leading punctuation' do
|
||||
Slug.for("...hello").should == "hello"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
|
|
@ -16,6 +16,35 @@ describe SiteCustomization do
|
|||
end
|
||||
|
||||
context 'caching' do
|
||||
|
||||
context 'enabled style' do
|
||||
before do
|
||||
@customization = customization
|
||||
end
|
||||
|
||||
it 'finds no style when none enabled' do
|
||||
SiteCustomization.enabled_style_key.should be_nil
|
||||
end
|
||||
|
||||
it 'finds the enabled style' do
|
||||
@customization.enabled = true
|
||||
@customization.save
|
||||
SiteCustomization.enabled_style_key.should == @customization.key
|
||||
end
|
||||
|
||||
it 'finds no enabled style on other sites' do
|
||||
@customization.enabled = true
|
||||
@customization.save
|
||||
|
||||
RailsMultisite::ConnectionManagement.expects(:current_db).returns("foo").twice
|
||||
# the mocking is tricky, lets remove the record so we can properly pretend we are on another db
|
||||
# this bypasses the before / after stuff
|
||||
SiteCustomization.exec_sql('delete from site_customizations')
|
||||
|
||||
SiteCustomization.enabled_style_key.should be_nil
|
||||
end
|
||||
end
|
||||
|
||||
it 'should allow me to lookup a filename containing my preview stylesheet' do
|
||||
SiteCustomization.custom_stylesheet(customization.key).should ==
|
||||
"<link class=\"custom-css\" rel=\"stylesheet\" href=\"/stylesheet-cache/#{customization.key}.css?#{customization.stylesheet_hash}\" type=\"text/css\" media=\"screen\">"
|
||||
|
|
|
@ -67,12 +67,8 @@ describe User do
|
|||
user.reload
|
||||
user.posts_read_count.should == 1
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
context '.enqueue_welcome_message' do
|
||||
|
@ -174,6 +170,29 @@ describe User do
|
|||
|
||||
end
|
||||
|
||||
describe 'delete posts' do
|
||||
before do
|
||||
@post1 = Fabricate(:post)
|
||||
@user = @post1.user
|
||||
@post2 = Fabricate(:post, topic: @post1.topic, user: @user)
|
||||
@post3 = Fabricate(:post, user: @user)
|
||||
@posts = [@post1, @post2, @post3]
|
||||
@guardian = Guardian.new(Fabricate(:admin))
|
||||
end
|
||||
|
||||
it 'allows moderator to delete all posts' do
|
||||
@user.delete_all_posts!(@guardian)
|
||||
@posts.each do |p|
|
||||
p.reload
|
||||
if p
|
||||
p.topic.should be_nil
|
||||
else
|
||||
p.should be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe 'new' do
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user