rainbows category:"parks and gardens" in:bookmarks ブックマークされている"parks and gardens"カテゴリの中で、"rainbows"という言葉を含むトピックを検索します。
+
+
badges:
+ editor:
+ name: 編集者
+ description: 最初の投稿を編集する
+ autobiographer:
+ name: あなたはだれ?
+ description: プロフィールをすべて書く
+ nice_post:
+ name: ナイスな返事
+ good_post:
+ name: イカす返事
+ great_post:
+ name: 素晴らしい返事
+ nice_topic:
+ name: ナイスなトピック
+ good_topic:
+ name: イカすトピック
first_flag:
name: はじめの通報
description: 通報した投稿
+ read_guidelines:
+ description: ガイドラインを読む
+ popular_link:
+ name: 人気のリンク
+ hot_link:
+ name: ウワサのリンク
+ famous_link:
+ name: 伝説のリンク
+ first_emoji:
+ name: はじめての絵文字
admin_login:
success: "メールを送信しました"
error: "エラー!"
diff --git a/config/locales/server.pt.yml b/config/locales/server.pt.yml
index 35f64b1d65..eb4c8272da 100644
--- a/config/locales/server.pt.yml
+++ b/config/locales/server.pt.yml
@@ -24,7 +24,7 @@ pt:
loading: "A carregar"
powered_by_html: 'Desenvolvido por Discourse, e melhor visualizado com o JavaScript ativo'
log_in: "Iniciar Sessão"
- purge_reason: "Automaticamente eliminado devido a abandono, conta inativada"
+ purge_reason: "Conta removida automaticamente como conta abandonada, desactivada."
disable_remote_images_download_reason: "O download remoto de imagens foi desativado por não haver espaço disponível no disco."
anonymous: "Anónimo"
emails:
@@ -35,7 +35,7 @@ pt:
empty_email_error: "Acontece quando, a informação não processada, do email recebido veio em branco."
no_message_id_error: "Acontece quando o email recebido não tem 'Message-Id' no cabeçalho da mensagem."
auto_generated_email_error: "Acontece quando o cabeçalho 'precedence' é definida como: 'list', 'junk' ou 'auto_reply', ou quando algum cabeçalho contém: 'auto-submitted', 'auto-replied' ou 'auto-generated'."
- no_body_detected_error: "Acontece quando não conseguimos extrair o corpo da mensagem e não existem anexos."
+ no_body_detected_error: "Acontece quando não se consegue obter um corpo de mensagem e não existem anexos."
inactive_user_error: "Acontece quando o remetente não está activo."
blocked_user_error: "Acontece quando o remetente está bloqueado."
bad_destination_address: "Acontece quando nenhum dos endereços de email nos campos para/cc/bcc coincide com um endereço de email configurado."
@@ -44,6 +44,9 @@ pt:
reply_user_not_matching_error: "Acontece quando uma resposta veio de um endereço de email diferente do destinatário da notificação."
topic_not_found_error: "Acontece quando a resposta veio de um tópico relacionado mas o tópico relacionado foi apagado."
topic_closed_error: "Acontece quando uma resposta chegou mas o tópico relacionado foi fechado."
+ bounced_email_error: "O email é um relatório devolvido."
+ auto_generated_email_reply: "O email contém uma resposta a um email gerado automaticamente."
+ screened_email_error: "Acontece quando a morada de email da pessoa que enviou já foi confirmada."
errors: &errors
format: '%{attribute} %{message}'
messages:
@@ -174,6 +177,7 @@ pt:
group_mentions: "Últimas menções em %{group_name}"
user_posts: "Mensagens mais recentes criados por @%{username}"
user_topics: "Tópicos mais recentes criados por @%{username}"
+ tag: "Tópicos etiquetados"
too_late_to_edit: "Essa mensagem foi criada há muito tempo. Já não pode ser editada ou apagada."
revert_version_same: "A versão atual é o mesma versão para a qual você está tentando reverter."
excerpt_image: "imagem"
@@ -181,7 +185,7 @@ pt:
delete_reason: "Eliminado através da fila de moderação de mensagens"
groups:
errors:
- can_not_modify_automatic: "Não pode modificar um grupo automático"
+ can_not_modify_automatic: "Não pode modificar um grupo automatico"
member_already_exist: "'%{username}' já é membro deste grupo."
invalid_domain: "'%{domain}' não é um domínio válido."
invalid_incoming_email: "'%{email}' não é um endereço de email válido."
@@ -475,7 +479,7 @@ pt:
continue_button: "Continuar para %{site_name}"
welcome_to: "Bem-vindo a %{site_name}!"
approval_required: "Um moderador tem que aprovar a sua conta antes de poder aceder a este fórum. Irá receber um email quando a sua conta for aprovada!"
- missing_session: "Não conseguimos detetar se a sua conta foi criada, por favor assegure-se que tem os cookies ativos."
+ missing_session: "Não conseguimos detectar se a sua conta foi criada, pelo que pedimos que confirme que tem os cookies possibilitados."
post_action_types:
off_topic:
title: 'Fora de Contexto'
@@ -721,6 +725,9 @@ pt:
email_polling_errored_recently:
one: "A consulta automática de emails gerou um erro nas últimas 24 horas. Consulte em os registos para mais detalhes."
other: "A consulta automática de emails gerou %{count} erros nas últimas 24 horas. Consulte em os registos para mais detalhes."
+ bad_favicon_url: "Não estamos a conseguir carregar o favicon. Pot favor, confirme a sua configuração favicon_url nas Configurações do sítio."
+ poll_pop3_timeout: "A tentativa de ligação ao servidor POP3 está a ultrapassar o tempo máximo. Email de caixa de entrada não pôde ser obtido. Por favor verifique a sua configuração de POP3 e fornecedor de serviço internet."
+ poll_pop3_auth_error: "A tentativa de ligação ao servidor POP3 está a falhar por motivos de erro de autenticação. Por favor verifique a sua configuração de POP3."
site_settings:
censored_words: "Palavras que serão automaticamente substituídas por ■■■■"
delete_old_hidden_posts: "Eliminar automaticamente quaisquer mensagens ocultas que permaneçam escondidas por mais de 30 dias."
@@ -765,6 +772,7 @@ pt:
post_onebox_maxlength: "Tamanho máximo de uma mensagem Discourse de caixa única, em caracteres."
onebox_domains_whitelist: "Lista de domínios que permitem colocar em caixa única; estes domínios devem suportar OpenGraph ou oEmbed. Teste-os em http://iframely.com/debug"
logo_url: "A imagem do logótipo no canto superior esquerdo do seu sítio, deve ter uma forma retangular. Quando deixada em branco, o texto do título do sítio será exibido."
+ digest_logo_url: "O logotipo alternativo usado no topo do email de resumo do seu sítio. Deve ter um formato rectangular amplo. Não pode ser uma imagem em formato SVG. Se deixado em branco, `logo_url` será utilizado."
logo_small_url: "A pequena imagem do logótipo no canto superior esquerdo do seu sítio, deve ter uma forma quadrada, visível quando arrastado para baixo. Quando deixado em branco, um glifo de início será exibido."
favicon_url: "Um favicon para o seu sítio, veja http://en.wikipedia.org/wiki/Favicon, para trabalhar corretamente sobre um CDN deve ser um png"
mobile_logo_url: "A imagem do logótipo com posição fixa utilizada no canto superior esquerdo do seu sítio móvel deve ter uma forma quadrada. Quando deixado em branco, `logo_url` será utilizado. ex: http://exemplo.com/uploads/default/logo.png"
@@ -785,6 +793,7 @@ pt:
polling_interval: "Quando não está a ocorrer uma solicitação ao servidor, com que frequência devem os clientes ligados requerer uma atualização, em milissegundos"
anon_polling_interval: "Com que frequência os clientes não registados podem fazer solicitações ao servidor, em milisegundos"
background_polling_interval: "Com que frequência deverão os clientes solicitar o servidor, em milissegundos (quando a janela está em plano de fundo)"
+ flags_required_to_hide_post: "Número de bandeiras que fazem com que uma mensagem seja automaticamente escondida e uma mensagem enviada ao utilizador (0 se nunca)"
cooldown_minutes_after_hiding_posts: "Número de minutos que o utilizador deve esperar antes de poder editar uma mensagem oculta devido a sinalizações por parte da comunidade"
max_topics_in_first_day: "O número máximo de tópicos que é o utilizador pode criar no seu primeiro dia no Fórum."
max_replies_in_first_day: "Número máximo de mensagens que um utilizador pode criar no seu primeiro dia no Fórum."
diff --git a/config/locales/server.pt_BR.yml b/config/locales/server.pt_BR.yml
index 5c22281a6e..e1065c534b 100644
--- a/config/locales/server.pt_BR.yml
+++ b/config/locales/server.pt_BR.yml
@@ -24,7 +24,7 @@ pt_BR:
loading: "Carregando"
powered_by_html: 'Desenvolvido por Discourse, melhor visualizado com JavaScript ativado'
log_in: "Entrar"
- purge_reason: "A conta não verificada, foi excluída."
+ purge_reason: "Automaticamente eliminada por ser uma conta abandonada, não ativada"
disable_remote_images_download_reason: "Download de imagens remotas foi desativado porque não havia espaço suficiente em disco disponível."
anonymous: "Anônimo"
emails:
diff --git a/config/locales/server.ro.yml b/config/locales/server.ro.yml
index d44a17e714..98c03ce65a 100644
--- a/config/locales/server.ro.yml
+++ b/config/locales/server.ro.yml
@@ -340,7 +340,7 @@ ro:
please_continue: "Noul dvs cont este confirmat, iar acum sunteți autentificat."
continue_button: "Continuă cu %{site_name}"
welcome_to: "Bine ați venit la %{site_name}!"
- approval_required: "Un moderator trebuie să aprobe manual contul înaine să puteți accesa forumul. Veți primii un email când acesta a fost aprobat!"
+ approval_required: "Un moderator trebuie să aprobe manual contul înaine să puteți accesa forumul. Vei primi un email când acesta a fost aprobat!"
post_action_types:
off_topic:
title: 'În afară discuției'
@@ -352,7 +352,7 @@ ro:
long_form: 'marchează aceasta ca spam'
inappropriate:
title: 'Necorespunzător'
- description: 'Această postare are conținut pe care o persoană normală l-ar numii ofesator, abuziv, sau o violare a regulilor comune.'
+ description: 'Această postare are conținut ce ar putea fi considerat drept ofesator, abuziv, sau o violare a regulilor comune.'
long_form: 'marcat ca necorespunzător'
notify_user:
email_title: 'Despre postarea dvs din "%{title}"'
@@ -379,7 +379,7 @@ ro:
long_form: 'Marchează asta ca spam'
inappropriate:
title: 'Necorespunzător'
- description: 'Această discuție are conținut pe care o persoană normală l-ar numii ofesantor, abuziv, sau o violare a regulilor comune.'
+ description: 'Această discuție are conținut ce ar putea fi considerat drept ofesantor, abuziv, sau o violare a regulilor comune.'
long_form: 'marcat ca necorespunzător'
notify_moderators:
title: "Notifică moderatori"
@@ -601,7 +601,7 @@ ro:
invite_expiry_days: "Cat timp sun valabile cheile de invitație ale utilizatorilor, în zile"
invite_passthrough_hours: "Cât timp un utilizator poate folosii o cheie de invitație recuperată anterior pentru autentificare, în ore"
invite_only: "Înregistrarea publică este dezactivată, toți utilizatorii noi trebuie să fie exclusiv invitați de un alt membru sau personalul site-ului."
- login_required: "Autentificarea e necesară pentru a citii conținutul site-ului, nu permite accesul anonim."
+ login_required: "Autentificarea este necesară pentru a citi conținutul site-ului, nu permite accesul anonim."
min_password_length: "Lungimea minimă de caractere pentru parolă."
block_common_passwords: "nu permite parole ce sunt în cele 10,000 cele mai cunoscute parole."
enable_local_logins: "Activează numele de utilizator local și a conturilor bazate pe logare cu parola. (Notă: Aceasta trebuie activiată pentru invitații pt a funcționa)"
@@ -874,7 +874,7 @@ ro:
previous_discussion: "Răspunsurile precedente"
unsubscribe:
title: "Dezabonare"
- description: "Nu sunteți interesat în a primii aceste email-uri? Nicio problemă! Faceți clic dedesubt pentru a vă dezabona imediat:"
+ description: "Nu ești interesat în a primi aceste email-uri? Nicio problemă! Faceți click dedesubt pentru a vă dezabona imediat:"
posted_by: "Postat de %{username} pe data %{post_date}"
user_replied:
subject_template: "[%{site_name}] %{topic_title}"
diff --git a/config/locales/server.ru.yml b/config/locales/server.ru.yml
index 606eb21828..213450abba 100644
--- a/config/locales/server.ru.yml
+++ b/config/locales/server.ru.yml
@@ -12,7 +12,7 @@ ru:
long_date: "D MMMM YYYY, HH:mm"
datetime_formats: &datetime_formats
formats:
- short: "%m-%d-%Y"
+ short: "%d.%m.%Y"
date:
month_names: [null, Январь, Февраль, Март, Апрель, Май, Июнь, Июль, Август, Сентябрь, Октябрь, Ноябрь, Декабрь]
<<: *datetime_formats
@@ -22,7 +22,7 @@ ru:
loading: "Загрузка..."
powered_by_html: 'При поддержке Discourse, лучше всего использовать с включенным JavaScript'
log_in: "Войти"
- purge_reason: "Автоматически удален, как неактивная, неактивированная учетная запись"
+ purge_reason: "Деактивированная учетная запись будет автоматически удалена как заброшенная"
disable_remote_images_download_reason: "Загрузка картинок была отключена из-за недостаточности места на диске."
anonymous: "Гость"
emails:
diff --git a/config/locales/server.zh_CN.yml b/config/locales/server.zh_CN.yml
index a9d2052a26..00a51dab52 100644
--- a/config/locales/server.zh_CN.yml
+++ b/config/locales/server.zh_CN.yml
@@ -2436,18 +2436,14 @@ zh_CN:
thank_you:
name: 感谢你
description: 有 20 个被赞的帖子,给出过 10 个赞
- long_description: |
- 该徽章授予给收到了 20 个赞,且赞过别人 10 次的你。当别人感谢你的帖子时,你也感谢他们的帖子。
gives_back:
name: 回馈
description: 有 100 个被赞的帖子,给出过 100 个赞
- long_description: |
- 该徽章授予给收到了 100 个赞,且赞过别人 100 次的你。感谢你回馈赞给社群!
empathetic:
name: 感性
description: 有 500 个被赞的帖子,给出过 1000 个赞
- long_description: |
- 该徽章授予给收到了 500 个赞,且赞过别人 1000 次的你。哇!你是一个富有同情心且会换位思考的模范。 :two_hearts:
+ first_emoji:
+ description: 在帖子中使用表情符号
admin_login:
success: "邮件已发送"
error: "错误!"
diff --git a/config/site_settings.yml b/config/site_settings.yml
index fc59c05897..a6a000ad4c 100644
--- a/config/site_settings.yml
+++ b/config/site_settings.yml
@@ -544,6 +544,8 @@ email:
pop3_polling_username: ''
pop3_polling_password: ''
log_mail_processing_failures: false
+ incoming_email_prefer_html:
+ default: false
email_in:
default: false
client: true
@@ -698,6 +700,7 @@ trust:
tl3_time_period:
default: 100
min: 1
+ max: 1000000
tl3_requires_days_visited:
default: 50
min: 0
@@ -708,10 +711,16 @@ trust:
default: 25
min: 0
max: 100
+ tl3_requires_topics_viewed_cap:
+ default: 500
+ min: 0
tl3_requires_posts_read:
default: 25
min: 0
max: 100
+ tl3_requires_posts_read_cap:
+ default: 20000
+ min: 0
tl3_requires_topics_viewed_all_time:
default: 200
min: 0
@@ -1128,6 +1137,9 @@ user_preferences:
default_email_private_messages: true
default_email_direct: true
default_email_mailing_list_mode: false
+ default_email_mailing_list_mode_frequency:
+ enum: 'MailingListModeSiteSetting'
+ default: 1
disable_mailing_list_mode:
default: false
client: true
diff --git a/config/unicorn_upstart.conf b/config/unicorn_upstart.conf
index 49cb2bb778..5f0e3c639e 100644
--- a/config/unicorn_upstart.conf
+++ b/config/unicorn_upstart.conf
@@ -23,7 +23,6 @@ exec /bin/bash <<'EOT'
# set HOME to the setuid user's home, there doesn't seem to be a better, portable way
export HOME="$(eval echo ~$(id -un))"
export RAILS_ENV=production
- export RUBY_GC_MALLOC_LIMIT=90000000
cd /var/www/discourse
diff --git a/db/migrate/20160309073132_add_mailing_list_mode_frequency.rb b/db/migrate/20160309073132_add_mailing_list_mode_frequency.rb
new file mode 100644
index 0000000000..dcdce07c40
--- /dev/null
+++ b/db/migrate/20160309073132_add_mailing_list_mode_frequency.rb
@@ -0,0 +1,5 @@
+class AddMailingListModeFrequency < ActiveRecord::Migration
+ def change
+ add_column :user_options, :mailing_list_mode_frequency, :integer, default: 0, null: false
+ end
+end
diff --git a/db/migrate/20160326001747_add_user_first_visit.rb b/db/migrate/20160326001747_add_user_first_visit.rb
new file mode 100644
index 0000000000..50e000ab00
--- /dev/null
+++ b/db/migrate/20160326001747_add_user_first_visit.rb
@@ -0,0 +1,5 @@
+class AddUserFirstVisit < ActiveRecord::Migration
+ def change
+ add_column :users, :first_seen_at, :datetime
+ end
+end
diff --git a/db/migrate/20160520022627_shorten_topic_custom_fields_index.rb b/db/migrate/20160520022627_shorten_topic_custom_fields_index.rb
new file mode 100644
index 0000000000..8226c7556f
--- /dev/null
+++ b/db/migrate/20160520022627_shorten_topic_custom_fields_index.rb
@@ -0,0 +1,12 @@
+class ShortenTopicCustomFieldsIndex < ActiveRecord::Migration
+ def up
+ remove_index :topic_custom_fields, :value
+ add_index :topic_custom_fields, [:value, :name],
+ name: 'topic_custom_fields_value_key_idx',
+ where: 'value IS NOT NULL AND char_length(value) < 400'
+ end
+ def down
+ remove_index :topic_custom_fields, :value, name: 'topic_custom_fields_value_key_idx'
+ add_index :topic_custom_fields, :value
+ end
+end
diff --git a/lib/backup_restore/backuper.rb b/lib/backup_restore/backuper.rb
index b55d735907..163bc84508 100644
--- a/lib/backup_restore/backuper.rb
+++ b/lib/backup_restore/backuper.rb
@@ -257,12 +257,12 @@ module BackupRestore
end
end
- log "Gzipping archive..."
+ log "Gzipping archive, this may take a while..."
`gzip -5 #{tar_filename}`
end
def after_create_hook
- log "Executing the after_create_hook for the backup"
+ log "Executing the after_create_hook for the backup..."
backup = Backup.create_from_filename("#{File.basename(@archive_basename)}.tar.gz")
backup.after_create_hook
end
diff --git a/lib/backup_restore/restorer.rb b/lib/backup_restore/restorer.rb
index 15c177240e..3b843998ab 100644
--- a/lib/backup_restore/restorer.rb
+++ b/lib/backup_restore/restorer.rb
@@ -161,7 +161,7 @@ module BackupRestore
end
def unzip_archive
- log "Unzipping archive..."
+ log "Unzipping archive, this may take a while..."
FileUtils.cd(@tmp_directory) { `gzip --decompress '#{@archive_filename}'` }
end
diff --git a/lib/cooked_post_processor.rb b/lib/cooked_post_processor.rb
index 9bb6b83b04..60af2b57ac 100644
--- a/lib/cooked_post_processor.rb
+++ b/lib/cooked_post_processor.rb
@@ -194,7 +194,10 @@ class CookedPostProcessor
original_width, original_height = get_size(src)
# can't reach the image...
- if original_width.nil? || original_height.nil?
+ if original_width.nil? ||
+ original_height.nil? ||
+ original_width == 0 ||
+ original_height == 0
Rails.logger.info "Can't reach '#{src}' to get its dimension."
return
end
@@ -204,8 +207,16 @@ class CookedPostProcessor
return if is_a_hyperlink?(img)
+ crop = false
+ if original_width.to_f / original_height.to_f < 0.75
+ crop = true
+ width, height = ImageSizer.crop(original_width, original_height)
+ img["width"] = width
+ img["height"] = height
+ end
+
if upload = Upload.get_from_url(src)
- upload.create_thumbnail!(width, height)
+ upload.create_thumbnail!(width, height, crop)
end
add_lightbox!(img, original_width, original_height, upload)
diff --git a/lib/email/receiver.rb b/lib/email/receiver.rb
index bc2b98005f..d71cf8ca7d 100644
--- a/lib/email/receiver.rb
+++ b/lib/email/receiver.rb
@@ -212,16 +212,21 @@ module Email
text = fix_charset(@mail)
end
- # prefer text over html
- text = trim_discourse_markers(text) if text.present?
- text, elided = EmailReplyTrimmer.trim(text, true) if text.present?
- return [text, elided] if text.present?
+ use_html = html.present? && (!text.present? || SiteSetting.incoming_email_prefer_html)
+ use_text = text.present? unless use_html
- # clean the html if that's all we've got
- html = Email::HtmlCleaner.new(html).output_html if html.present?
- html = trim_discourse_markers(html) if html.present?
- html, elided = EmailReplyTrimmer.trim(html, true) if html.present?
- return [html, elided] if html.present?
+ if use_text
+ text = trim_discourse_markers(text)
+ text, elided = EmailReplyTrimmer.trim(text, true)
+ return [text, elided]
+ end
+
+ if use_html
+ html = Email::HtmlCleaner.new(html).output_html
+ html = trim_discourse_markers(html)
+ html, elided = EmailReplyTrimmer.trim(html, true)
+ return [html, elided]
+ end
end
def fix_charset(mail_part)
diff --git a/lib/email/styles.rb b/lib/email/styles.rb
index a82c8459f3..a8d646ddff 100644
--- a/lib/email/styles.rb
+++ b/lib/email/styles.rb
@@ -6,6 +6,10 @@ module Email
class Styles
@@plugin_callbacks = []
+ attr_accessor :fragment
+
+ delegate :css, to: :fragment
+
def initialize(html, opts=nil)
@html = html
@opts = opts || {}
@@ -33,7 +37,7 @@ module Email
@fragment.css('img').each do |img|
next if img['class'] == 'site-logo'
- if img['class'] == "emoji" || img['src'] =~ /plugins\/emoji/
+ if img['class'] == "emoji" || img['src'] =~ /(plugins|images)\/emoji/
img['width'] = 20
img['height'] = 20
else
@@ -90,6 +94,7 @@ module Email
style('.rtl', 'direction: rtl;')
style('td.body', 'padding-top:5px;', colspan: "2")
style('.whisper td.body', 'font-style: italic; color: #9c9c9c;')
+ style('.lightbox-wrapper .meta', 'display: none')
correct_first_body_margin
correct_footer_style
reset_tables
@@ -186,6 +191,17 @@ module Email
@fragment.to_s
end
+ def make_all_links_absolute
+ site_uri = URI(Discourse.base_url)
+ @fragment.css("a").each do |link|
+ begin
+ link["href"] = "#{site_uri}#{link['href']}" unless URI(link["href"].to_s).host.present?
+ rescue URI::InvalidURIError, URI::InvalidComponentError
+ # leave it
+ end
+ end
+ end
+
private
def replace_relative_urls
diff --git a/lib/es6_module_transpiler/tilt/es6_module_transpiler_template.rb b/lib/es6_module_transpiler/tilt/es6_module_transpiler_template.rb
index 58eb0e678c..68b2ca990b 100644
--- a/lib/es6_module_transpiler/tilt/es6_module_transpiler_template.rb
+++ b/lib/es6_module_transpiler/tilt/es6_module_transpiler_template.rb
@@ -1,22 +1,9 @@
require 'execjs'
require 'babel/transpiler'
+require 'mini_racer'
module Tilt
- class Console
- def initialize(prefix=nil)
- @prefix = prefix || ''
- end
-
- def log(msg)
- Rails.logger.info("#{@prefix}#{msg}")
- end
-
- def error(msg)
- Rails.logger.error("#{@prefix}#{msg}")
- end
- end
-
class ES6ModuleTranspilerTemplate < Tilt::Template
self.default_mime_type = 'application/javascript'
@@ -30,10 +17,19 @@ module Tilt
def self.create_new_context
# timeout any eval that takes longer than 15 seconds
- ctx = V8::Context.new(timeout: 15000)
+ ctx = MiniRacer::Context.new(timeout: 15000)
ctx.eval("var self = this; #{File.read(Babel::Transpiler.script_path)}")
ctx.eval("module = {}; exports = {};");
ctx.load("#{Rails.root}/lib/es6_module_transpiler/support/es6-module-transpiler.js")
+ ctx.attach("rails.logger.info", proc{|err| Rails.logger.info(err.to_s)})
+ ctx.attach("rails.logger.error", proc{|err| Rails.logger.error(err.to_s)})
+ ctx.eval < e
- raise JavaScriptError.new(e.message, e.backtrace)
- end
+ yield
end
- rval
end
def whitelisted?(path)
@@ -98,7 +84,7 @@ module Tilt
def babel_transpile(source)
klass = self.class
klass.protect do
- klass.v8['console'] = Console.new("BABEL: babel-eval: ")
+ klass.v8.eval("console.prefix = 'BABEL: babel-eval: ';")
@output = klass.v8.eval(babel_source(source))
end
end
@@ -108,7 +94,7 @@ module Tilt
klass = self.class
klass.protect do
- klass.v8['console'] = Console.new("BABEL: #{scope.logical_path}: ")
+ klass.v8.eval("console.prefix = 'BABEL: #{scope.logical_path}: ';")
@output = klass.v8.eval(generate_source(scope))
end
diff --git a/lib/file_store/local_store.rb b/lib/file_store/local_store.rb
index a4068e57d1..e9bbdab6a1 100644
--- a/lib/file_store/local_store.rb
+++ b/lib/file_store/local_store.rb
@@ -12,11 +12,10 @@ module FileStore
def remove_file(url)
return unless is_relative?(url)
path = public_dir + url
+ return if !File.exists?(path)
tombstone = public_dir + url.sub("/uploads/", "/tombstone/")
FileUtils.mkdir_p(Pathname.new(tombstone).dirname)
FileUtils.move(path, tombstone, :force => true)
- rescue Errno::ENOENT
- # don't care if the file isn't there
end
def has_been_uploaded?(url)
diff --git a/lib/image_sizer.rb b/lib/image_sizer.rb
index cc4ee8a6b5..3cf9f126fb 100644
--- a/lib/image_sizer.rb
+++ b/lib/image_sizer.rb
@@ -16,4 +16,19 @@ module ImageSizer
[(w * ratio).floor, (h * ratio).floor]
end
+ def self.crop(width, height, opts = {})
+ return if width.blank? || height.blank?
+
+ max_width = (opts[:max_width] || SiteSetting.max_image_width).to_f
+ max_height = (opts[:max_height] || SiteSetting.max_image_height).to_f
+
+ w = width.to_f
+ h = height.to_f
+
+ return [w.floor, h.floor] if w <= max_width && h <= max_height
+
+ ratio = max_width / w
+ [max_width.floor, [max_height, (h * ratio)].min.floor]
+ end
+
end
diff --git a/lib/javascripts/moment.js b/lib/javascripts/moment.js
index d5e925c1d5..7f1d25a4c5 100644
--- a/lib/javascripts/moment.js
+++ b/lib/javascripts/moment.js
@@ -1171,10 +1171,7 @@
}
utils_hooks__hooks.createFromInputFallback = deprecate(
- 'moment construction falls back to js Date. This is ' +
- 'discouraged and will be removed in upcoming major ' +
- 'release. Please refer to ' +
- 'https://github.com/moment/moment/issues/1407 for more info.',
+ 'moment construction falls back to js Date. This is discouraged and will be removed in upcoming major release. Please refer to https://github.com/moment/moment/issues/1407 for more info.',
function (config) {
config._d = new Date(config._i + (config._useUTC ? ' UTC' : ''));
}
diff --git a/lib/js_locale_helper.rb b/lib/js_locale_helper.rb
index db4821a37b..eb2f1a19d3 100644
--- a/lib/js_locale_helper.rb
+++ b/lib/js_locale_helper.rb
@@ -154,15 +154,25 @@ module JsLocaleHelper
result
end
- def self.compile_message_format(locale, format)
- ctx = V8::Context.new
- ctx.load(Rails.root + 'lib/javascripts/messageformat.js')
- path = Rails.root + "lib/javascripts/locale/#{locale}.js"
- ctx.load(path) if File.exists?(path)
- ctx.eval("mf = new MessageFormat('#{locale}');")
- ctx.eval("mf.precompile(mf.parse(#{format.inspect}))")
+ @mutex = Mutex.new
+ def self.with_context
+ @mutex.synchronize do
+ yield @ctx ||= begin
+ ctx = MiniRacer::Context.new
+ ctx.load(Rails.root + 'lib/javascripts/messageformat.js')
+ ctx
+ end
+ end
+ end
- rescue V8::Error => e
+ def self.compile_message_format(locale, format)
+ with_context do |ctx|
+ path = Rails.root + "lib/javascripts/locale/#{locale}.js"
+ ctx.load(path) if File.exists?(path)
+ ctx.eval("mf = new MessageFormat('#{locale}');")
+ ctx.eval("mf.precompile(mf.parse(#{format.inspect}))")
+ end
+ rescue MiniRacer::EvalError => e
message = "Invalid Format: " << e.message
"function(){ return #{message.inspect};}"
end
diff --git a/lib/pretty_text.rb b/lib/pretty_text.rb
index bfdcbbcd92..e1341006f3 100644
--- a/lib/pretty_text.rb
+++ b/lib/pretty_text.rb
@@ -1,4 +1,4 @@
-require 'v8'
+require 'mini_racer'
require 'nokogiri'
require_dependency 'url_helper'
require_dependency 'excerpt_parser'
@@ -7,7 +7,9 @@ require_dependency 'discourse_tagging'
module PrettyText
- class Helpers
+ module Helpers
+ extend self
+
def t(key, opts)
key = "js." + key
unless opts
@@ -81,6 +83,7 @@ module PrettyText
nil
end
end
+
end
@mutex = Mutex.new
@@ -92,9 +95,11 @@ module PrettyText
def self.create_new_context
# timeout any eval that takes longer than 15 seconds
- ctx = V8::Context.new(timeout: 15000)
+ ctx = MiniRacer::Context.new(timeout: 15000)
- ctx["helpers"] = Helpers.new
+ Helpers.instance_methods.each do |method|
+ ctx.attach("helpers.#{method}", Helpers.method(method))
+ end
ctx_load(ctx,
"vendor/assets/javascripts/md5.js",
@@ -198,6 +203,7 @@ module PrettyText
# we use the exact same markdown converter as the client
# TODO: use the same extensions on both client and server (in particular the template for mentions)
baked = nil
+ text = text || ""
protect do
context = v8
@@ -206,8 +212,9 @@ module PrettyText
context_opts = opts || {}
context_opts[:sanitize] = true unless context_opts[:sanitize] == false
- context['opts'] = context_opts
- context['raw'] = text
+
+ context.eval("opts = #{context_opts.to_json};")
+ context.eval("raw = #{text.inspect};")
if Post.white_listed_image_classes.present?
Post.white_listed_image_classes.each do |klass|
@@ -258,8 +265,10 @@ module PrettyText
# leaving this here, cause it invokes v8, don't want to implement twice
def self.avatar_img(avatar_template, size)
protect do
- v8['avatarTemplate'] = avatar_template
- v8['size'] = size
+ v8.eval < e
- raise JavaScriptError.new(e.message, e.backtrace)
- end
+ rval = yield
end
rval
end
diff --git a/lib/tasks/avatars.rake b/lib/tasks/avatars.rake
index c27dac792f..4c1da8fd54 100644
--- a/lib/tasks/avatars.rake
+++ b/lib/tasks/avatars.rake
@@ -21,9 +21,9 @@ task "avatars:clean" => :environment do
puts "Cleaning up avatar thumbnails"
puts
- OptimizedImage.where("upload_id IN (SELECT custom_upload_id FROM user_avatars)")
- .where("upload_id IN (SELECT gravatar_upload_id FROM user_avatars)")
- .where("upload_id IN (SELECT uploaded_avatar_id FROM users)")
+ OptimizedImage.where("upload_id IN (SELECT custom_upload_id FROM user_avatars) OR
+ upload_id IN (SELECT gravatar_upload_id FROM user_avatars) OR
+ upload_id IN (SELECT uploaded_avatar_id FROM users)")
.find_each do |optimized_image|
optimized_image.destroy!
putc "." if (i += 1) % 10 == 0
diff --git a/lib/tasks/docker.rake b/lib/tasks/docker.rake
index 0045fcee42..8e847c698f 100644
--- a/lib/tasks/docker.rake
+++ b/lib/tasks/docker.rake
@@ -15,7 +15,7 @@ task 'docker:test' do
puts "Starting background redis"
@redis_pid = Process.spawn('redis-server --dir tmp/test_data/redis')
- @postgres_bin = "/usr/lib/postgresql/9.3/bin/"
+ @postgres_bin = "/usr/lib/postgresql/9.5/bin/"
`#{@postgres_bin}initdb -D tmp/test_data/pg`
# speed up db, never do this in production mmmmk
diff --git a/lib/version.rb b/lib/version.rb
index 146324f3b4..919b48142d 100644
--- a/lib/version.rb
+++ b/lib/version.rb
@@ -5,7 +5,7 @@ module Discourse
MAJOR = 1
MINOR = 6
TINY = 0
- PRE = 'beta5'
+ PRE = 'beta6'
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
end
diff --git a/plugins/poll/config/locales/client.bs_BA.yml b/plugins/poll/config/locales/client.bs_BA.yml
index c03c38e593..da40be0d95 100644
--- a/plugins/poll/config/locales/client.bs_BA.yml
+++ b/plugins/poll/config/locales/client.bs_BA.yml
@@ -17,5 +17,8 @@ bs_BA:
few: "ukupno glasova"
other: "ukupno glasova"
average_rating: "Prosječna ocjena: %{average}."
+ cast-votes:
+ title: "ukupno glasova"
+ label: "Glasaj"
close:
label: "Zatvori"
diff --git a/plugins/poll/config/locales/client.fr.yml b/plugins/poll/config/locales/client.fr.yml
index cb7bcccbd1..a674b4e69b 100644
--- a/plugins/poll/config/locales/client.fr.yml
+++ b/plugins/poll/config/locales/client.fr.yml
@@ -9,8 +9,8 @@ fr:
js:
poll:
voters:
- one: "voteur"
- other: "voteurs"
+ one: "votant"
+ other: "votants"
total_votes:
one: "vote au total"
other: "votes au total"
diff --git a/plugins/poll/config/locales/client.ja.yml b/plugins/poll/config/locales/client.ja.yml
index a3b3f1bbb4..b9be616a8b 100644
--- a/plugins/poll/config/locales/client.ja.yml
+++ b/plugins/poll/config/locales/client.ja.yml
@@ -24,7 +24,7 @@ ja:
between_min_and_max_options: "%{min} と %{max} のオプションから選択することができます。"
cast-votes:
title: "投票する"
- label: "すぐ投票!"
+ label: "今すぐ投票!"
show-results:
title: "投票結果を表示"
label: "結果を表示"
diff --git a/plugins/poll/config/locales/server.ja.yml b/plugins/poll/config/locales/server.ja.yml
index 7b42aca449..16434983fa 100644
--- a/plugins/poll/config/locales/server.ja.yml
+++ b/plugins/poll/config/locales/server.ja.yml
@@ -26,9 +26,9 @@ ja:
cannot_change_polls_after_5_minutes: "最初の5分を経過すると、投票の追加、削除や名前変更はできません。"
op_cannot_edit_options_after_5_minutes: "最初の5分を経過すると投票オプションの追加や削除はできません。投票オプションの編集が必要であれば、モデレータに連絡してください。"
staff_cannot_add_or_remove_options_after_5_minutes: "最初の5分を経過すると投票オプションの追加や削除はできません。このトピックを閉じて新しいトピックを作成してください。"
- no_polls_associated_with_this_post: "このポストに関連付けられた投票はありません。"
- no_poll_with_this_name: "このポストに関連付けられた投票 %{name} はありません。"
- post_is_deleted: "削除されたポストに作用することはできません。"
+ no_polls_associated_with_this_post: "この投稿に関連付けられた投票はありません。"
+ no_poll_with_this_name: "この投稿に関連付けられた投票 %{name} はありません。"
+ post_is_deleted: "削除された投稿を操作する事はできません。"
topic_must_be_open_to_vote: "投票するトピックはオープンになっている必要があります。"
poll_must_be_open_to_vote: "投票するにはオープンになっている必要があります。"
topic_must_be_open_to_toggle_status: "状態を切り替えるには、トピックがオープンになっている必要があります。"
diff --git a/public/500.ja.html b/public/500.ja.html
index 9e698a3fec..48faa075b9 100644
--- a/public/500.ja.html
+++ b/public/500.ja.html
@@ -5,7 +5,7 @@
diff --git a/script/bench.rb b/script/bench.rb
index ea79eae9a6..61a3857b81 100644
--- a/script/bench.rb
+++ b/script/bench.rb
@@ -111,7 +111,6 @@ discourse_env_vars = %w(DISCOURSE_DUMP_HEAP RUBY_GC_HEAP_INIT_SLOTS RUBY_GC_HEAP
if @include_env
puts "Running with tuned environment"
- ENV["RUBY_GC_MALLOC_LIMIT"] = "50_000_000"
discourse_env_vars - %w(RUBY_GC_MALLOC_LIMIT).each do |v|
ENV.delete v
end
diff --git a/script/import_scripts/punbb.rb b/script/import_scripts/punbb.rb
index a5d54372f1..6b8fad7f50 100644
--- a/script/import_scripts/punbb.rb
+++ b/script/import_scripts/punbb.rb
@@ -43,7 +43,7 @@ class ImportScripts::PunBB < ImportScripts::Base
break if results.size < 1
- next if all_records_exist? :users, users.map {|u| u["id"].to_i}
+ next if all_records_exist? :users, results.map {|u| u["id"].to_i}
create_users(results, total: total_count, offset: offset) do |user|
{ id: user['id'],
diff --git a/script/import_scripts/vanilla_mysql.rb b/script/import_scripts/vanilla_mysql.rb
index aba0dd9423..9bf33fd637 100644
--- a/script/import_scripts/vanilla_mysql.rb
+++ b/script/import_scripts/vanilla_mysql.rb
@@ -320,7 +320,9 @@ class ImportScripts::VanillaSQL < ImportScripts::Base
raw.gsub!(/\[attach[^\]]*\]\d+\[\/attach\]/i, "")
# sanitize img tags
- raw.gsub!(/\/i) {"\n\n"}
+ # This regexp removes everything between the first and last img tag. The .* is too much.
+ # If it's needed, it needs to be fixed.
+ # raw.gsub!(/\/i) {"\n\n"}
raw
end
diff --git a/spec/components/cooked_post_processor_spec.rb b/spec/components/cooked_post_processor_spec.rb
index 27e6e3f823..02e36bc8a5 100644
--- a/spec/components/cooked_post_processor_spec.rb
+++ b/spec/components/cooked_post_processor_spec.rb
@@ -110,7 +110,7 @@ describe CookedPostProcessor do
SiteSetting.create_thumbnails = true
Upload.expects(:get_from_url).returns(upload)
- FastImage.stubs(:size).returns([1000, 2000])
+ FastImage.stubs(:size).returns([1750, 2000])
# hmmm this should be done in a cleaner way
OptimizedImage.expects(:resize).returns(true)
@@ -120,8 +120,8 @@ describe CookedPostProcessor do
it "generates overlay information" do
cpp.post_process_images
- expect(cpp.html).to match_html '
'
expect(cpp).to be_dirty
end
@@ -143,7 +143,7 @@ describe CookedPostProcessor do
Discourse.stubs(:base_uri).returns(base_uri)
Upload.expects(:get_from_url).returns(upload)
- FastImage.stubs(:size).returns([1000, 2000])
+ FastImage.stubs(:size).returns([1750, 2000])
# hmmm this should be done in a cleaner way
OptimizedImage.expects(:resize).returns(true)
@@ -153,8 +153,8 @@ describe CookedPostProcessor do
it "generates overlay information" do
cpp.post_process_images
- expect(cpp.html).to match_html '
'
expect(cpp).to be_dirty
end
@@ -172,7 +172,7 @@ describe CookedPostProcessor do
SiteSetting.create_thumbnails = true
Upload.expects(:get_from_url).returns(upload)
- FastImage.stubs(:size).returns([1000, 2000])
+ FastImage.stubs(:size).returns([1750, 2000])
# hmmm this should be done in a cleaner way
OptimizedImage.expects(:resize).returns(true)
@@ -181,8 +181,8 @@ describe CookedPostProcessor do
it "generates overlay information" do
cpp.post_process_images
- expect(cpp.html).to match_html '
'
expect(cpp).to be_dirty
end
diff --git a/spec/components/email/receiver_spec.rb b/spec/components/email/receiver_spec.rb
index f996028ec1..7a802acd1d 100644
--- a/spec/components/email/receiver_spec.rb
+++ b/spec/components/email/receiver_spec.rb
@@ -171,6 +171,18 @@ describe Email::Receiver do
expect(topic.posts.last.raw).to eq("This is the *text* part.")
end
+ it "prefers html over text when site setting is enabled" do
+ SiteSetting.incoming_email_prefer_html = true
+ expect { process(:text_and_html_reply) }.to change { topic.posts.count }
+ expect(topic.posts.last.raw).to eq('This is the html part.')
+ end
+
+ it "uses text when prefer_html site setting is enabled but no html is available" do
+ SiteSetting.incoming_email_prefer_html = true
+ expect { process(:text_reply) }.to change { topic.posts.count }
+ expect(topic.posts.last.raw).to eq("This is a text reply :)")
+ end
+
it "removes the 'on , wrote' quoting line" do
expect { process(:on_date_contact_wrote) }.to change { topic.posts.count }
expect(topic.posts.last.raw).to eq("This is the actual reply.")
diff --git a/spec/components/file_store/local_store_spec.rb b/spec/components/file_store/local_store_spec.rb
index 28f741f1aa..68724c5777 100644
--- a/spec/components/file_store/local_store_spec.rb
+++ b/spec/components/file_store/local_store_spec.rb
@@ -40,6 +40,7 @@ describe FileStore::LocalStore do
it "moves the file to the tombstone" do
FileUtils.expects(:mkdir_p)
FileUtils.expects(:move)
+ File.expects(:exists?).returns(true)
upload = Upload.new
upload.stubs(:url).returns("/uploads/default/42/253dc8edf9d4ada1.png")
store.remove_upload(upload)
@@ -52,6 +53,7 @@ describe FileStore::LocalStore do
it "moves the file to the tombstone" do
FileUtils.expects(:mkdir_p)
FileUtils.expects(:move)
+ File.expects(:exists?).returns(true)
oi = OptimizedImage.new
oi.stubs(:url).returns("/uploads/default/_optimized/42/253dc8edf9d4ada1.png")
store.remove_optimized_image(upload)
diff --git a/spec/components/js_locale_helper_spec.rb b/spec/components/js_locale_helper_spec.rb
index 1b26efe88c..125474f93d 100644
--- a/spec/components/js_locale_helper_spec.rb
+++ b/spec/components/js_locale_helper_spec.rb
@@ -1,5 +1,6 @@
require 'rails_helper'
require_dependency 'js_locale_helper'
+require 'mini_racer'
describe JsLocaleHelper do
@@ -25,7 +26,7 @@ describe JsLocaleHelper do
end
def setup_message_format(format)
- @ctx = V8::Context.new
+ @ctx = MiniRacer::Context.new
@ctx.eval('MessageFormat = {locale: {}};')
@ctx.load(Rails.root + 'lib/javascripts/locale/en.js')
compiled = JsLocaleHelper.compile_message_format('en', format)
@@ -72,7 +73,7 @@ describe JsLocaleHelper do
end
it 'handles message format special keys' do
- ctx = V8::Context.new
+ ctx = MiniRacer::Context.new
ctx.eval("I18n = {};")
JsLocaleHelper.set_translations 'en', {
@@ -149,7 +150,7 @@ describe JsLocaleHelper do
SiteSetting.default_locale = 'ru'
I18n.locale = :uk
- ctx = V8::Context.new
+ ctx = MiniRacer::Context.new
ctx.eval('var window = this;')
ctx.load(Rails.root + 'app/assets/javascripts/locales/i18n.js')
ctx.eval(JsLocaleHelper.output_locale(I18n.locale))
@@ -167,7 +168,7 @@ describe JsLocaleHelper do
LocaleSiteSetting.values.each do |locale|
it "generates valid date helpers for #{locale[:value]} locale" do
js = JsLocaleHelper.output_locale(locale[:value])
- ctx = V8::Context.new
+ ctx = MiniRacer::Context.new
ctx.eval('var window = this;')
ctx.load(Rails.root + 'app/assets/javascripts/locales/i18n.js')
ctx.eval(js)
diff --git a/spec/components/pretty_text_spec.rb b/spec/components/pretty_text_spec.rb
index 63e2eca471..d66de21387 100644
--- a/spec/components/pretty_text_spec.rb
+++ b/spec/components/pretty_text_spec.rb
@@ -286,41 +286,6 @@ HTML
end
end
- describe "make_all_links_absolute" do
- let(:base_url) { "http://baseurl.net" }
-
- def make_abs_string(html)
- doc = Nokogiri::HTML.fragment(html)
- described_class.make_all_links_absolute(doc)
- doc.to_html
- end
-
- before do
- Discourse.stubs(:base_url).returns(base_url)
- end
-
- it "adds base url to relative links" do
- html = "
"
- expect { make_abs_string(html) }.to_not raise_error
- end
- end
-
describe "strip_image_wrapping" do
def strip_image_wrapping(html)
doc = Nokogiri::HTML.fragment(html)
@@ -339,8 +304,36 @@ HTML
end
describe 'format_for_email' do
+ let(:base_url) { "http://baseurl.net" }
+ let(:post) { Fabricate(:post) }
+
+ before do
+ Discourse.stubs(:base_url).returns(base_url)
+ end
+
it 'does not crash' do
- PrettyText.format_for_email('test')
+ PrettyText.format_for_email('test', post)
+ end
+
+ it "adds base url to relative links" do
+ html = "
"
+ expect { described_class.format_for_email(html, post) }.to_not raise_error
end
end
diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb
index 43887f1ea5..9edaa64734 100644
--- a/spec/controllers/users_controller_spec.rb
+++ b/spec/controllers/users_controller_spec.rb
@@ -1516,6 +1516,7 @@ describe UsersController do
describe ".is_local_username" do
let(:user) { Fabricate(:user) }
+ let(:group) { Fabricate(:group, name: "Discourse") }
it "finds the user" do
xhr :get, :is_local_username, username: user.username
@@ -1524,6 +1525,13 @@ describe UsersController do
expect(json["valid"][0]).to eq(user.username)
end
+ it "finds the group" do
+ xhr :get, :is_local_username, username: group.name
+ expect(response).to be_success
+ json = JSON.parse(response.body)
+ expect(json["valid_groups"][0]).to eq(group.name)
+ end
+
it "supports multiples usernames" do
xhr :get, :is_local_username, usernames: [user.username, "system"]
expect(response).to be_success
diff --git a/spec/jobs/enqueue_mailing_list_emails_spec.rb b/spec/jobs/enqueue_mailing_list_emails_spec.rb
new file mode 100644
index 0000000000..820ed1e812
--- /dev/null
+++ b/spec/jobs/enqueue_mailing_list_emails_spec.rb
@@ -0,0 +1,128 @@
+require 'rails_helper'
+require_dependency 'jobs/base'
+
+describe Jobs::EnqueueMailingListEmails do
+
+ describe '#target_users' do
+
+ context 'unapproved users' do
+ Given!(:unapproved_user) { Fabricate(:active_user, approved: false, first_seen_at: 24.hours.ago) }
+ When do
+ SiteSetting.must_approve_users = true
+ unapproved_user.user_option.update(mailing_list_mode: true, mailing_list_mode_frequency: 0)
+ end
+ Then { expect(Jobs::EnqueueMailingListEmails.new.target_user_ids.include?(unapproved_user.id)).to eq(false) }
+
+ # As a moderator
+ And { unapproved_user.update_column(:moderator, true) }
+ And { expect(Jobs::EnqueueMailingListEmails.new.target_user_ids.include?(unapproved_user.id)).to eq(true) }
+
+ # As an admin
+ And { unapproved_user.update_attributes(admin: true, moderator: false) }
+ And { expect(Jobs::EnqueueMailingListEmails.new.target_user_ids.include?(unapproved_user.id)).to eq(true) }
+
+ # As an approved user
+ And { unapproved_user.update_attributes(admin: false, moderator: false, approved: true ) }
+ And { expect(Jobs::EnqueueMailingListEmails.new.target_user_ids.include?(unapproved_user.id)).to eq(true) }
+ end
+
+ context 'staged users' do
+ let!(:staged_user) { Fabricate(:active_user, staged: true, last_emailed_at: 1.year.ago, last_seen_at: 1.year.ago) }
+
+ it "doesn't return staged users" do
+ expect(Jobs::EnqueueMailingListEmails.new.target_user_ids.include?(staged_user.id)).to eq(false)
+ end
+ end
+
+ context "inactive user" do
+ let!(:inactive_user) { Fabricate(:user, active: false) }
+
+ it "doesn't return users who have been emailed recently" do
+ expect(Jobs::EnqueueMailingListEmails.new.target_user_ids.include?(inactive_user.id)).to eq(false)
+ end
+ end
+
+ context "suspended user" do
+ let!(:suspended_user) { Fabricate(:user, suspended_till: 1.week.from_now, suspended_at: 1.day.ago) }
+
+ it "doesn't return users who are suspended" do
+ expect(Jobs::EnqueueMailingListEmails.new.target_user_ids.include?(suspended_user.id)).to eq(false)
+ end
+ end
+
+ context 'users with mailing list mode on' do
+ let(:user) { Fabricate(:active_user, first_seen_at: 24.hours.ago) }
+ let(:user_option) { user.user_option }
+ subject { Jobs::EnqueueMailingListEmails.new.target_user_ids }
+ before do
+ user_option.update(mailing_list_mode: true, mailing_list_mode_frequency: 0)
+ end
+
+ it "returns a user whose first_seen_at matches the current hour" do
+ expect(subject).to include user.id
+ end
+
+ it "returns a user seen multiple days ago" do
+ user.update(first_seen_at: 72.hours.ago)
+ expect(subject).to include user.id
+ end
+
+ it "doesn't return a user who has never been seen" do
+ user.update(first_seen_at: nil)
+ expect(subject).to_not include user.id
+ end
+
+ it "doesn't return users with mailing list mode off" do
+ user_option.update(mailing_list_mode: false)
+ expect(subject).to_not include user.id
+ end
+
+ it "doesn't return users with mailing list mode set to 'individual'" do
+ user_option.update(mailing_list_mode_frequency: 1)
+ expect(subject).to_not include user.id
+ end
+
+ it "doesn't return a user who has received the mailing list summary earlier" do
+ user.update(first_seen_at: 5.hours.ago)
+ expect(subject).to_not include user.id
+ end
+
+ it "doesn't return a user who was first seen today" do
+ user.update(first_seen_at: 2.minutes.ago)
+ expect(subject).to_not include user.id
+ end
+ end
+
+ end
+
+ describe '#execute' do
+
+ let(:user) { Fabricate(:user) }
+
+ context "mailing list emails are enabled" do
+ before do
+ Jobs::EnqueueMailingListEmails.any_instance.expects(:target_user_ids).returns([user.id])
+ end
+
+ it "enqueues the mailing list email job" do
+ Jobs.expects(:enqueue).with(:user_email, type: :mailing_list, user_id: user.id)
+ Jobs::EnqueueMailingListEmails.new.execute({})
+ end
+ end
+
+ context "mailing list emails are disabled" do
+ before do
+ Jobs::EnqueueMailingListEmails.any_instance.expects(:target_user_ids).never
+ end
+
+ it "does not enqueue the mailing list email job" do
+ SiteSetting.disable_mailing_list_mode = true
+ Jobs.expects(:enqueue).with(:user_email, type: :mailing_list, user_id: user.id).never
+ Jobs::EnqueueMailingListEmails.new.execute({})
+ end
+ end
+
+ end
+
+
+end
diff --git a/spec/jobs/notify_mailing_list_subscribers_spec.rb b/spec/jobs/notify_mailing_list_subscribers_spec.rb
index f63b4225cc..75e32073d9 100644
--- a/spec/jobs/notify_mailing_list_subscribers_spec.rb
+++ b/spec/jobs/notify_mailing_list_subscribers_spec.rb
@@ -34,10 +34,17 @@ describe Jobs::NotifyMailingListSubscribers do
context "with a valid post" do
let!(:post) { Fabricate(:post, user: user) }
- it "sends the email to the user" do
+ it "sends the email to the user if the frequency is set to 'always'" do
+ user.user_option.update(mailing_list_mode: true, mailing_list_mode_frequency: 1)
UserNotifications.expects(:mailing_list_notify).with(user, post).once
Jobs::NotifyMailingListSubscribers.new.execute(post_id: post.id)
end
+
+ it "does not send the email to the user if the frequency is set to 'daily'" do
+ user.user_option.update(mailing_list_mode: true, mailing_list_mode_frequency: 0)
+ UserNotifications.expects(:mailing_list_notify).never
+ Jobs::NotifyMailingListSubscribers.new.execute(post_id: post.id)
+ end
end
context "with a deleted post" do
diff --git a/spec/mailers/user_notifications_spec.rb b/spec/mailers/user_notifications_spec.rb
index 00ceb74a3a..c1f0c6f065 100644
--- a/spec/mailers/user_notifications_spec.rb
+++ b/spec/mailers/user_notifications_spec.rb
@@ -76,6 +76,69 @@ describe UserNotifications do
end
+ describe '.mailing_list' do
+ subject { UserNotifications.mailing_list(user) }
+
+ context "without new posts" do
+ it "doesn't send the email" do
+ expect(subject.to).to be_blank
+ end
+ end
+
+ context "with new posts" do
+ let(:user) { Fabricate(:user) }
+ let(:topic) { Fabricate(:topic, user: user) }
+ let!(:new_post) { Fabricate(:post, topic: topic, created_at: 2.hours.ago, raw: "Feel the Bern") }
+ let!(:old_post) { Fabricate(:post, topic: topic, created_at: 25.hours.ago, raw: "Make America Great Again") }
+ let(:old_topic) { Fabricate(:topic, user: user, created_at: 10.days.ago) }
+ let(:new_post_in_old_topic) { Fabricate(:post, topic: old_topic, created_at: 2.hours.ago, raw: "Yes We Can") }
+ let(:stale_post) { Fabricate(:post, topic: old_topic, created_at: 2.days.ago, raw: "A New American Century") }
+
+ it "works" do
+ expect(subject.to).to eq([user.email])
+ expect(subject.subject).to be_present
+ expect(subject.from).to eq([SiteSetting.notification_email])
+ expect(subject.html_part.body.to_s).to include topic.title
+ expect(subject.text_part.body.to_s).to be_present
+ end
+
+ it "includes posts less than 24 hours old" do
+ expect(subject.html_part.body.to_s).to include new_post.cooked
+ end
+
+ it "does not include posts older than 24 hours old" do
+ expect(subject.html_part.body.to_s).to_not include old_post.cooked
+ end
+
+ it "includes topics created over 24 hours ago which have new posts" do
+ new_post_in_old_topic
+ expect(subject.html_part.body.to_s).to include old_topic.title
+ expect(subject.html_part.body.to_s).to include new_post_in_old_topic.cooked
+ expect(subject.html_part.body.to_s).to_not include stale_post.cooked
+ end
+
+ it "includes multiple topics" do
+ new_post_in_old_topic
+ expect(subject.html_part.body.to_s).to include topic.title
+ expect(subject.html_part.body.to_s).to include old_topic.title
+ end
+
+ it "does not include topics not updated for the past 24 hours" do
+ stale_post
+ expect(subject.html_part.body.to_s).to_not include old_topic.title
+ expect(subject.html_part.body.to_s).to_not include stale_post.cooked
+ end
+
+ it "includes email_prefix in email subject instead of site title" do
+ SiteSetting.email_prefix = "Try Discourse"
+ SiteSetting.title = "Discourse Meta"
+
+ expect(subject.subject).to match(/Try Discourse/)
+ expect(subject.subject).not_to match(/Discourse Meta/)
+ end
+ end
+ end
+
describe '.digest' do
subject { UserNotifications.digest(user) }
diff --git a/spec/models/admin_dashboard_data_spec.rb b/spec/models/admin_dashboard_data_spec.rb
index 507518a36c..eae7f68079 100644
--- a/spec/models/admin_dashboard_data_spec.rb
+++ b/spec/models/admin_dashboard_data_spec.rb
@@ -70,20 +70,6 @@ describe AdminDashboardData do
end
end
- describe 'gc_checks' do
- subject { described_class.new.gc_checks }
-
- it 'returns nil when gc params are set' do
- ENV.stubs(:[]).with('RUBY_GC_MALLOC_LIMIT').returns(90000000)
- expect(subject).to be_nil
- end
-
- it 'returns a string when gc params are not set' do
- ENV.stubs(:[]).with('RUBY_GC_MALLOC_LIMIT').returns(nil)
- expect(subject).to_not be_nil
- end
- end
-
describe 'sidekiq_check' do
subject { described_class.new.sidekiq_check }
diff --git a/spec/models/mailing_list_mode_site_setting_spec.rb b/spec/models/mailing_list_mode_site_setting_spec.rb
new file mode 100644
index 0000000000..d8ccc7e4aa
--- /dev/null
+++ b/spec/models/mailing_list_mode_site_setting_spec.rb
@@ -0,0 +1,17 @@
+require 'rails_helper'
+
+describe MailingListModeSiteSetting do
+ describe 'valid_value?' do
+ it 'returns true for a valid value as an int' do
+ expect(DigestEmailSiteSetting.valid_value?(0)).to eq true
+ end
+
+ it 'returns false for a valid value as a string' do
+ expect(DigestEmailSiteSetting.valid_value?('0')).to eq true
+ end
+
+ it 'returns false for an out of range value' do
+ expect(DigestEmailSiteSetting.valid_value?(3)).to eq false
+ end
+ end
+end
diff --git a/spec/models/trust_level3_requirements_spec.rb b/spec/models/trust_level3_requirements_spec.rb
index 764aef8ac9..46e3d89992 100644
--- a/spec/models/trust_level3_requirements_spec.rb
+++ b/spec/models/trust_level3_requirements_spec.rb
@@ -35,12 +35,26 @@ describe TrustLevel3Requirements do
expect(tl3_requirements.min_topics_viewed).to eq(23)
end
+ it "min_topics_viewed is capped" do
+ SiteSetting.tl3_requires_topics_viewed = 75
+ described_class.stubs(:num_topics_in_time_period).returns(31)
+ SiteSetting.tl3_requires_topics_viewed_cap = 20
+ expect(tl3_requirements.min_topics_viewed).to eq(20)
+ end
+
it "min_posts_read depends on site setting and number of posts created" do
SiteSetting.stubs(:tl3_requires_posts_read).returns(66)
described_class.stubs(:num_posts_in_time_period).returns(1234)
expect(tl3_requirements.min_posts_read).to eq(814)
end
+ it "min_posts_read is capped" do
+ SiteSetting.tl3_requires_posts_read = 66
+ described_class.stubs(:num_posts_in_time_period).returns(1234)
+ SiteSetting.tl3_requires_posts_read_cap = 600
+ expect(tl3_requirements.min_posts_read).to eq(600)
+ end
+
it "min_topics_viewed_all_time depends on site setting" do
SiteSetting.stubs(:tl3_requires_topics_viewed_all_time).returns(75)
expect(tl3_requirements.min_topics_viewed_all_time).to eq(75)
@@ -243,7 +257,7 @@ describe TrustLevel3Requirements do
end
end
- describe "requirements" do
+ context "requirements with defaults" do
before do
tl3_requirements.stubs(:min_days_visited).returns(50)
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 92e12e9e71..63a0dcfb2a 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -596,6 +596,29 @@ describe User do
end
+ describe "update_last_seen!" do
+ let (:user) { Fabricate(:user) }
+ let!(:first_visit_date) { Time.zone.now }
+ let!(:second_visit_date) { 2.hours.from_now }
+
+ it "should update the last seen value" do
+ expect(user.last_seen_at).to eq nil
+ user.update_last_seen!(first_visit_date)
+ expect(user.reload.last_seen_at).to be_within_one_second_of(first_visit_date)
+ end
+
+ it "should update the first seen value if it doesn't exist" do
+ user.update_last_seen!(first_visit_date)
+ expect(user.reload.first_seen_at).to be_within_one_second_of(first_visit_date)
+ end
+
+ it "should not update the first seen value if it doesn't exist" do
+ user.update_last_seen!(first_visit_date)
+ user.update_last_seen!(second_visit_date)
+ expect(user.reload.first_seen_at).to be_within_one_second_of(first_visit_date)
+ end
+ end
+
describe "last_seen_at" do
let(:user) { Fabricate(:user) }
diff --git a/spec/phantom_js/smoke_test.js b/spec/phantom_js/smoke_test.js
index 779b686eca..4b4db60c9b 100644
--- a/spec/phantom_js/smoke_test.js
+++ b/spec/phantom_js/smoke_test.js
@@ -190,6 +190,10 @@ var runTests = function() {
return document.querySelector(".topic-list");
});
+ test('we have a create topic button', function() {
+ return document.querySelector("#create-topic");
+ });
+
exec("open composer", function() {
$("#create-topic").click();
});
diff --git a/test/javascripts/lib/utilities-test.js.es6 b/test/javascripts/lib/utilities-test.js.es6
index 3cea33cbed..c9f35a948b 100644
--- a/test/javascripts/lib/utilities-test.js.es6
+++ b/test/javascripts/lib/utilities-test.js.es6
@@ -92,7 +92,7 @@ var getUploadMarkdown = function(filename) {
test("getUploadMarkdown", function() {
ok(getUploadMarkdown("lolcat.gif") === '');
- ok(getUploadMarkdown("important.txt") === 'important.txt (42 Bytes)');
+ ok(getUploadMarkdown("important.txt") === 'important.txt (42 Bytes)\n');
});
test("isAnImage", function() {