diff --git a/app/assets/stylesheets/common/admin/admin_base.scss b/app/assets/stylesheets/common/admin/admin_base.scss
index 6659f61a7f..580ff074e5 100644
--- a/app/assets/stylesheets/common/admin/admin_base.scss
+++ b/app/assets/stylesheets/common/admin/admin_base.scss
@@ -1449,8 +1449,8 @@ table#user-badges {
}
}
-.url-list {
- .url {
+.value-list {
+ .value {
border-bottom: 1px solid #ddd;
padding: 3px;
margin-right: 10px;
@@ -1460,7 +1460,7 @@ table#user-badges {
text-overflow: ellipsis;
}
- .urls {
+ .values {
margin-bottom: 10px;
}
diff --git a/app/controllers/embed_controller.rb b/app/controllers/embed_controller.rb
index 249a18f7a7..198288297a 100644
--- a/app/controllers/embed_controller.rb
+++ b/app/controllers/embed_controller.rb
@@ -53,8 +53,8 @@ class EmbedController < ApplicationController
def ensure_embeddable
if !(Rails.env.development? && current_user.try(:admin?))
- raise Discourse::InvalidAccess.new('embeddable host not set') if SiteSetting.normalized_embeddable_host.blank?
- raise Discourse::InvalidAccess.new('invalid referer host') if URI(request.referer || '').host != SiteSetting.normalized_embeddable_host
+ raise Discourse::InvalidAccess.new('embeddable hosts not set') if SiteSetting.embeddable_hosts.blank?
+ raise Discourse::InvalidAccess.new('invalid referer host') unless SiteSetting.allows_embeddable_host?(request.referer)
end
response.headers['X-Frame-Options'] = "ALLOWALL"
diff --git a/app/models/site_setting.rb b/app/models/site_setting.rb
index 5b272c0d91..02dad62554 100644
--- a/app/models/site_setting.rb
+++ b/app/models/site_setting.rb
@@ -68,9 +68,16 @@ class SiteSetting < ActiveRecord::Base
@anonymous_menu_items ||= Set.new Discourse.anonymous_filters.map(&:to_s)
end
- def self.normalized_embeddable_host
- return embeddable_host if embeddable_host.blank?
- embeddable_host.sub(/^https?\:\/\//, '')
+ def self.allows_embeddable_host?(host)
+ return false if embeddable_hosts.blank?
+ uri = URI(host) rescue nil
+
+ return false unless uri.present?
+
+ host = uri.host
+ return false unless host.present?
+
+ !!embeddable_hosts.split("\n").detect {|h| h.sub(/^https?\:\/\//, '') == host }
end
def self.anonymous_homepage
diff --git a/app/models/topic.rb b/app/models/topic.rb
index 1d0f6e4573..d9ebbd3af1 100644
--- a/app/models/topic.rb
+++ b/app/models/topic.rb
@@ -842,7 +842,7 @@ class Topic < ActiveRecord::Base
end
def expandable_first_post?
- SiteSetting.embeddable_host.present? && SiteSetting.embed_truncate? && has_topic_embed?
+ SiteSetting.embeddable_hosts.present? && SiteSetting.embed_truncate? && has_topic_embed?
end
private
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 4034dd8b64..f4a746ba60 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -2292,6 +2292,7 @@ en:
no_results: "No results found."
clear_filter: "Clear"
add_url: "add URL"
+ add_host: "add host"
categories:
all_results: 'All'
required: 'Required'
diff --git a/config/locales/server.bs_BA.yml b/config/locales/server.bs_BA.yml
index e492bd653c..930e3ae556 100644
--- a/config/locales/server.bs_BA.yml
+++ b/config/locales/server.bs_BA.yml
@@ -738,7 +738,7 @@ bs_BA:
default_code_lang: "Default programming language syntax highlighting applied to GitHub code blocks (lang-auto, ruby, python etc.)"
warn_reviving_old_topic_age: "When someone starts replying to a topic where the last reply is older than this many days, a warning will be displayed. Disable by setting to 0."
autohighlight_all_code: "Force apply code highlighting to all preformatted code blocks even when they didn't explicitly specify the language."
- embeddable_host: "Host that can embed the comments from this Discourse forum."
+ embeddable_hosts: "Host that can embed the comments from this Discourse forum."
feed_polling_enabled: "EMBEDDING ONLY: Whether to embed a RSS/ATOM feed as posts."
feed_polling_url: "EMBEDDING ONLY: URL of RSS/ATOM feed to embed."
embed_by_username: "Discourse username of the user who creates the embedded topics."
diff --git a/config/locales/server.de.yml b/config/locales/server.de.yml
index cf2a6222b4..9630552949 100644
--- a/config/locales/server.de.yml
+++ b/config/locales/server.de.yml
@@ -937,7 +937,7 @@ de:
warn_reviving_old_topic_age: "Wenn jemand beginnt auf ein Thema zu antworten, dessen letzte Antwort älter als diese Anzahl an Tagen ist, wird eine Warnung angezeigt. Deaktiviere dies durch setzen auf 0."
autohighlight_all_code: "Erzwinge Syntaxhervorhebung für alle Quellcode-Blöcke, auch dann wenn keine Sprache angeben wurde."
highlighted_languages: "Es wurden Syntaxregeln zur Hervorhebung von Textstellen hinzugefügt. (Achtung: Werden zu viele Sprachen hinzugefügt, kann das die Performance beeinflussen) siehe: https://highlightjs.org/static/demo/ für eine Demo."
- embeddable_host: "Host, der Kommentare aus diesem Discourse Forum einbetten darf. Nur Hostname ohne http://"
+ embeddable_hosts: "Host, der Kommentare aus diesem Discourse Forum einbetten darf. Nur Hostname ohne http://"
feed_polling_enabled: "NUR WENN EINGEBETTET: Bestimmt, ob Inhalte eines RSS-/ATOM-Feeds als zusätzliche Beiträge dargestellt werden."
feed_polling_url: "NUR WENN EINGEBETTET: URL des einzubettenden RSS-/ATOM-Feeds."
embed_by_username: "Discourse-Benutzername des Benutzers, der die eingebetteten Themen erstellt."
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index fc8525206f..bb6ce41067 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -1126,7 +1126,7 @@ en:
autohighlight_all_code: "Force apply code highlighting to all preformatted code blocks even when they didn't explicitly specify the language."
highlighted_languages: "Included syntax highlighting rules. (Warning: including too many langauges may impact performance) see: https://highlightjs.org/static/demo/ for a demo"
- embeddable_host: "Host that can embed the comments from this Discourse forum. Hostname only, do not begin with http://"
+ embeddable_hosts: "Host(s) that can embed the comments from this Discourse forum. Hostname only, do not begin with http://"
feed_polling_enabled: "EMBEDDING ONLY: Whether to embed a RSS/ATOM feed as posts."
feed_polling_url: "EMBEDDING ONLY: URL of RSS/ATOM feed to embed."
embed_by_username: "Discourse username of the user who creates the embedded topics."
diff --git a/config/locales/server.es.yml b/config/locales/server.es.yml
index 707c6b407b..d9808f08f7 100644
--- a/config/locales/server.es.yml
+++ b/config/locales/server.es.yml
@@ -959,7 +959,7 @@ es:
warn_reviving_old_topic_age: "Cuando alguien publica en un tema cuya última respuesta fue hace este número de días o más, se le mostrará un aviso para desalentar el hecho de resucitar una antigua discusión. Deshabilita esta opción introduciendo el valor 0."
autohighlight_all_code: "Forzar el resaltado de código a los bloques de código preformateado cuando no se especifique el lenguaje del código."
highlighted_languages: "Incluye reglas resaltadas de sintaxis. (Advertencia: incluyendo demasiadas lenguages puede afectar al rendimiento) ver: https://highlightjs.org/static/demo/ para una demostración"
- embeddable_host: "Host que puede incrustar los comentarios de este foro Discourse. Nombre de host solamente, no comienzan con http://"
+ embeddable_hosts: "Host que puede incrustar los comentarios de este foro Discourse. Nombre de host solamente, no comienzan con http://"
feed_polling_enabled: "SOLO PARA EMBEBER: embeber feeds RSS/ATOM como posts."
feed_polling_url: "SOLO PARA EMBEBER: URL de los feeds RSS/ATOM a embeber."
embed_by_username: "Nombre de usuario en Discourse del que crea los temas embebidos."
diff --git a/config/locales/server.fa_IR.yml b/config/locales/server.fa_IR.yml
index 7b80c9f829..f1b1be2b99 100644
--- a/config/locales/server.fa_IR.yml
+++ b/config/locales/server.fa_IR.yml
@@ -916,7 +916,7 @@ fa_IR:
warn_reviving_old_topic_age: "وقتی کسی شروع می کند با پاسخ دادن به جستاری که آخرین پاسخ برمی گردد به خیلی قبل یک هشدار نمایش داده می شود. نمایش با تنظیمات تا 0. "
autohighlight_all_code: "اعمال زور برای برجسته کردن کد به تمام بلاک های کد تنظیم نشده حتی وقتی به صراحت زبان را مشخص نمی کنند. "
highlighted_languages: "شامل نحوه قوانین برجسته شده. ( اخطار: شامل زبان های متفاوت ممکن است در نحوه اجرا تاثیر گذار باشد) ببین: https://highlightjs.org/static/demo/ برای دمو"
- embeddable_host: "سروری که می تونه در نوشته ها جا سازی بشه از انجمن دیسکورس. فقط نام سروری که با http:// شروع می شود"
+ embeddable_hosts: "سروری که می تونه در نوشته ها جا سازی بشه از انجمن دیسکورس. فقط نام سروری که با http:// شروع می شود"
feed_polling_enabled: "فقط جاسازی: چه جاسازی RSS/ATOM feed به عنوان نوشته"
feed_polling_url: "فقط جاسازی: URL of RSS/ATOM feed to embed."
embed_by_username: "نام کاربری دیسکورس کاربری که نوشته های جاسازی شده را ساخته."
diff --git a/config/locales/server.fi.yml b/config/locales/server.fi.yml
index 2d73da7ff6..be1af1cbc8 100644
--- a/config/locales/server.fi.yml
+++ b/config/locales/server.fi.yml
@@ -962,7 +962,7 @@ fi:
warn_reviving_old_topic_age: "Kun käyttäjä alkaa kirjoittamaan vastausta ketjuun, jonka uusin viesti on tätä vanhempi päivissä, näytetään varoitus. Poista käytöstä asettamalla arvoksi 0."
autohighlight_all_code: "Pakota koodin korostus kaikkiin esimuotoiltuihin tekstiblokkeihin, vaikka käyttäjä ei määrittelisi kieltä."
highlighted_languages: "Syntaksin korostamisen säännöt. (Varoitus: liian monen kielen sisällyttäminen voi vaikuttaa suorituskykyyn) katso demo: https://highlightjs.org/static/demo/"
- embeddable_host: "Isäntä, joka voi upottaa kommentteja tältä palstalta. Pelkkä isäntänimi, älä aloita http://"
+ embeddable_hosts: "Isäntä, joka voi upottaa kommentteja tältä palstalta. Pelkkä isäntänimi, älä aloita http://"
feed_polling_enabled: "VAIN UPOTUS: Upotetaanko RSS/ATOM syöte viesteinä."
feed_polling_url: "VAIN UPOTUS: RSS/ATOM syötteen URL."
embed_by_username: "Sen käyttäjän Discourse käyttäjänimi, joka luo upotetut ketjut."
diff --git a/config/locales/server.fr.yml b/config/locales/server.fr.yml
index 6eba5d754d..380ff34846 100644
--- a/config/locales/server.fr.yml
+++ b/config/locales/server.fr.yml
@@ -963,7 +963,7 @@ fr:
warn_reviving_old_topic_age: "Lorsque quelqu'un commence à répondre à un sujet dont la dernière réponse est vielle de plusieurs jours, un avertissement sera affiché. Désactiver la fonctionnalité en indiquant: 0."
autohighlight_all_code: "Forcer la mise en évidence de tout les textes dans les balises code, même si ils ne correspondent à aucun langage de programmation."
highlighted_languages: "Include les règles de mise en surbrillance de syntaxe. (Avertissement: l'ajout de trop de langages peut impacter les performances) voir l'exemple https://highlightjs.org/static/demo/"
- embeddable_host: "Hôte qui peut incorporer des messages de ce forum Discourse. Nom d'hôte seulement, sans http://"
+ embeddable_hosts: "Hôte qui peut incorporer des messages de ce forum Discourse. Nom d'hôte seulement, sans http://"
feed_polling_enabled: "EMBARQUER UNIQUEMENT: Embarqué le flux RSS/ATOM en tant que messages."
feed_polling_url: "EMBARQUER UNIQUEMENT: Url du flux RSS/ATOM à embarqué."
embed_by_username: "Pseudo de l'utilisateur Discourse qui crée les sujets embarqués."
diff --git a/config/locales/server.he.yml b/config/locales/server.he.yml
index 4b89ec7e52..b16af98532 100644
--- a/config/locales/server.he.yml
+++ b/config/locales/server.he.yml
@@ -925,7 +925,7 @@ he:
warn_reviving_old_topic_age: "כאשר מישהו/מישהי מתחילים להגיב לנושא שבו התגובה האחרונה היא בת יותר מכמה ימים, אזהרה תוצג. בטלו אפשרות זו באמצעות הזנה של 0."
autohighlight_all_code: "לחייב שימוש בקוד הדגשה לכל קוד מעוצב מראש (preformatted code blocks) אפילו אם הם אינם מציינים את השפה."
highlighted_languages: "הכללת הדגשת שגיאות תחביר. (אזהרה: הכללת שפות רבות מידי עשוי להשפיע על הביצוע) ראו: https://highlightjs.org/static/demo/ להדגמה"
- embeddable_host: "מארח (Host) אשר יכול להטמיע את התגובות מפורום Discourse זה. שם מארח בלבד, ללא http:// בהתחלה"
+ embeddable_hosts: "מארח (Host) אשר יכול להטמיע את התגובות מפורום Discourse זה. שם מארח בלבד, ללא http:// בהתחלה"
feed_polling_enabled: "הטמעה בלבד: האם לעמבד פידים של RSS/ATOM כפרסומים."
feed_polling_url: "הטמעה בלבד: URL של פיד RSS/ATOM להטמעה."
embed_by_username: "שם המשתמש של המשתמש/ת שיוצר את הנושאים המוטמעים."
diff --git a/config/locales/server.ja.yml b/config/locales/server.ja.yml
index 2a20f5ba6d..3544584d66 100644
--- a/config/locales/server.ja.yml
+++ b/config/locales/server.ja.yml
@@ -837,7 +837,7 @@ ja:
warn_reviving_old_topic_age: "最後の返信がこの設定よりも古いトピックに返信すると、警告を表示します。0を設定すると無効になります"
autohighlight_all_code: "明示的に言語を指定しなくても、全てのコードブロックにコードハイライトを強制的に適用する"
highlighted_languages: "適用するシンタックスハイライトルール(警告: あまりに多くの言語を含むとパフォーマンスに影響を与えます) デモ: https://highlightjs.org/static/demo/ "
- embeddable_host: "このDiscourseフォーラムのコメントを埋め込む事が出来るHost。Host名のみ、http:// で始めない"
+ embeddable_hosts: "このDiscourseフォーラムのコメントを埋め込む事が出来るHost。Host名のみ、http:// で始めない"
feed_polling_enabled: "EMBEDDING ONLY: ポストとしてRSS/Atomフィードを埋め込むかどうか"
feed_polling_url: "EMBEDDING ONLY: RSS/ATOMフィードのURLを埋め込む事が出来ます"
embed_by_username: "embedされたトピックの作成者として表示されるDiscourseユーザー名"
diff --git a/config/locales/server.pt.yml b/config/locales/server.pt.yml
index 2dc6087fb2..1bb604cc61 100644
--- a/config/locales/server.pt.yml
+++ b/config/locales/server.pt.yml
@@ -950,7 +950,7 @@ pt:
warn_reviving_old_topic_age: "Quando alguém começa a responder a um tópico em que a última resposta é mais antiga que estes dias, um aviso será exibido. Desativar ao configurar para 0."
autohighlight_all_code: "Forçar o destaque do código a todos os blocos de código pré-formatados mesmo quando não se especifica a linguagem."
highlighted_languages: "Incluídas regras de destaque da sintaxe (Aviso: incluir demasiadas linguagens pode impactar o desempenho) ver: https://highlightjs.org/static/demo/ para uma demonstração"
- embeddable_host: "Servidor que pode incorporar os comentários deste fórum Discourse. Apenas o nome do servidor, não começar com http://"
+ embeddable_hosts: "Servidor que pode incorporar os comentários deste fórum Discourse. Apenas o nome do servidor, não começar com http://"
feed_polling_enabled: "INCORPORAR APENAS: incorporar feeds RSS/ATOM como mensagens."
feed_polling_url: "INCORPORAR APENAS: URL dos feeds de RSS/ATOM para embutir."
embed_by_username: "Nome de utilizador Discourse do utilizador que cria tópicos embebidos."
diff --git a/config/locales/server.pt_BR.yml b/config/locales/server.pt_BR.yml
index 9e6ed66873..07e62e329d 100644
--- a/config/locales/server.pt_BR.yml
+++ b/config/locales/server.pt_BR.yml
@@ -950,7 +950,7 @@ pt_BR:
warn_reviving_old_topic_age: "Quando alguém começa a responder a um tópico mais velho do que este número de dias, um aviso será exibido para desencorajar o usuário de reviver uma velha discussão. Desabilite definindo para 0."
autohighlight_all_code: "Aplicar código destacando todos os blocos de código pré-formatados, mesmo quando não for específica o idioma"
highlighted_languages: "Incluir regras de destaque de sintaxe. (AVISO: incluir muitas linguagens podem afetar a performance) veja: https://highlightjs.org/static/demo/ para uma demonstração"
- embeddable_host: "Servidor que pode incorporar os comentários deste forum Discourse. Apenas o Nome do Servidor, não começar com http://"
+ embeddable_hosts: "Servidor que pode incorporar os comentários deste forum Discourse. Apenas o Nome do Servidor, não começar com http://"
feed_polling_enabled: "Se um feed RSS / ATOM são importados como mensagens"
feed_polling_url: "URL do feed RSS / ATOM para importar"
embed_by_username: "Nome de usuário Discourse para o usuário que cria os tópicos"
diff --git a/config/locales/server.ru.yml b/config/locales/server.ru.yml
index 2f988dea9e..f0a862aa69 100644
--- a/config/locales/server.ru.yml
+++ b/config/locales/server.ru.yml
@@ -1005,7 +1005,7 @@ ru:
warn_reviving_old_topic_age: "Показывать предупреждение, когда кто-то пытается ответить в очень старую тему. Указывается в днях. Чтобы отключить 0."
autohighlight_all_code: "Принудительно использовать подсветку кода для всех отформатированных блоков кода, даже когда они явно не указан язык."
highlighted_languages: "Включить правила подсветки синтаксиса. (ВНИМАНИЕ: включение для многих языков может вызвать проблемы с производительностью) пример можно посмотреть на: https://highlightjs.org/static/demo/ "
- embeddable_host: "Имя хоста которому разрешено использовать комменты с данного форума. Не указывайте http://"
+ embeddable_hosts: "Имя хоста которому разрешено использовать комменты с данного форума. Не указывайте http://"
feed_polling_enabled: "ТОЛЬКО ДЛЯ ВЛОЖЕННЫХ: Встраивать ли вложенные сообщения в RSS/ATOM ленту"
feed_polling_url: "URL адрес импорта RSS/ATOM ленты"
embed_by_username: "Имя пользователя который созал вложенную тему"
diff --git a/config/locales/server.sq.yml b/config/locales/server.sq.yml
index ed59ff9198..c692d86810 100644
--- a/config/locales/server.sq.yml
+++ b/config/locales/server.sq.yml
@@ -938,7 +938,7 @@ sq:
warn_reviving_old_topic_age: "When someone starts replying to a topic where the last reply is older than this many days, a warning will be displayed. Disable by setting to 0."
autohighlight_all_code: "Force apply code highlighting to all preformatted code blocks even when they didn't explicitly specify the language."
highlighted_languages: "Included syntax highlighting rules. (Warning: including too many langauges may impact performance) see: https://highlightjs.org/static/demo/ for a demo"
- embeddable_host: "Host that can embed the comments from this Discourse forum. Hostname only, do not begin with http://"
+ embeddable_hosts: "Host that can embed the comments from this Discourse forum. Hostname only, do not begin with http://"
feed_polling_enabled: "EMBEDDING ONLY: Whether to embed a RSS/ATOM feed as posts."
feed_polling_url: "EMBEDDING ONLY: URL of RSS/ATOM feed to embed."
embed_by_username: "Discourse username of the user who creates the embedded topics."
diff --git a/config/locales/server.tr_TR.yml b/config/locales/server.tr_TR.yml
index eea2bfc804..ee726c2a3d 100644
--- a/config/locales/server.tr_TR.yml
+++ b/config/locales/server.tr_TR.yml
@@ -903,7 +903,7 @@ tr_TR:
warn_reviving_old_topic_age: "Herhangi bir kullanıcı, son cevabın burada belirtilen gün sayısından daha önce yazıldığı bir konuya cevap yazmaya başladığında, bir uyarı mesajı çıkacak. Bu özelliği devre dışı bırakmak için 0 girin. "
autohighlight_all_code: "Tüm önceden formatlanan kod bloklarına, açıkça dil seçimi yapılmamış olsa da, zorla kod vurgulaması uygula."
highlighted_languages: "Dahil edilen sözdizimi vurgulama kuralları. (Dikkat: çok fazla dili dahil etmek performansı etkileyebilir) Demo için https://highlightjs.org/static/demo/ adresine bakınız"
- embeddable_host: "Bu Discourse forumundan yorumların yerleştirilebileceği sunucu. Sadece sunucu, http:// ile başlamayın"
+ embeddable_hosts: "Bu Discourse forumundan yorumların yerleştirilebileceği sunucu. Sadece sunucu, http:// ile başlamayın"
feed_polling_enabled: "SADECE YERLEŞTİRME İÇİN: RSS/ATOM beslemesinin gönderi olarak yerleştirilip yerleştirilemeyeceği."
feed_polling_url: "SADECE YERLEŞTİRME İÇİN: Yerleştirilecek RSS/ATOM beslemesinin URL'i."
embed_by_username: "Yerleştirilmiş konuları oluşturan kullanıcıya ait Discourse kullanıcı adı. "
diff --git a/config/locales/server.zh_CN.yml b/config/locales/server.zh_CN.yml
index 0af033048a..6bb6b7ded3 100644
--- a/config/locales/server.zh_CN.yml
+++ b/config/locales/server.zh_CN.yml
@@ -934,7 +934,7 @@ zh_CN:
warn_reviving_old_topic_age: "当有人开始回复最后一贴超过一定天数前的主题时,将有一个警告显示,不鼓励他们复活一个老的讨论。将其设置为 0 以禁用。"
autohighlight_all_code: "即使未显式设定语言,仍为所有预编排代码块应用语法高亮。"
highlighted_languages: "包含语法高亮规则。(警告:包含太多的语言可能会印象性能)见:https://highlightjs.org/static/demo/ 查看演示"
- embeddable_host: "能从这个 Discourse 论坛嵌入评论的主机。\n仅主机名,不要以 http:// 开头"
+ embeddable_hosts: "能从这个 Discourse 论坛嵌入评论的主机。\n仅主机名,不要以 http:// 开头"
feed_polling_enabled: "仅用于嵌入:是否将 RSS/ATOM 订阅为帖子。"
feed_polling_url: "仅用于嵌入:RSS/ATOM 订阅的 URL。"
embed_by_username: "创建嵌入主题的 Discourse 的用户名。"
diff --git a/config/site_settings.yml b/config/site_settings.yml
index c514c80297..8f2d141fef 100644
--- a/config/site_settings.yml
+++ b/config/site_settings.yml
@@ -729,9 +729,9 @@ developer:
client: true
embedding:
- embeddable_host:
- default: ''
- regex: "^(?!http).+" # don't allow this to start with http:// or https://
+ embeddable_hosts:
+ default: ''
+ type: host_list
feed_polling_enabled: false
feed_polling_url: ''
embed_by_username:
diff --git a/db/migrate/20150609163211_migrate_embeddable_host.rb b/db/migrate/20150609163211_migrate_embeddable_host.rb
new file mode 100644
index 0000000000..d3d351a32b
--- /dev/null
+++ b/db/migrate/20150609163211_migrate_embeddable_host.rb
@@ -0,0 +1,5 @@
+class MigrateEmbeddableHost < ActiveRecord::Migration
+ def change
+ execute "UPDATE site_settings SET name = 'embeddable_hosts', data_type = 9 WHERE name = 'embeddable_host'"
+ end
+end
diff --git a/lib/site_setting_extension.rb b/lib/site_setting_extension.rb
index 456f94d336..facb22bb4b 100644
--- a/lib/site_setting_extension.rb
+++ b/lib/site_setting_extension.rb
@@ -14,7 +14,7 @@ module SiteSettingExtension
end
def types
- @types ||= Enum.new(:string, :time, :fixnum, :float, :bool, :null, :enum, :list, :url_list)
+ @types ||= Enum.new(:string, :time, :fixnum, :float, :bool, :null, :enum, :list, :url_list, :host_list)
end
def mutex
diff --git a/lib/topic_retriever.rb b/lib/topic_retriever.rb
index 161f362be4..1682377309 100644
--- a/lib/topic_retriever.rb
+++ b/lib/topic_retriever.rb
@@ -13,10 +13,7 @@ class TopicRetriever
private
def invalid_host?
- SiteSetting.normalized_embeddable_host != URI(@embed_url).host
- rescue URI::InvalidURIError
- # An invalid URI is an invalid host
- true
+ SiteSetting.allows_embeddable_host?(@embed_url)
end
def retrieved_recently?
diff --git a/spec/controllers/embed_controller_spec.rb b/spec/controllers/embed_controller_spec.rb
index e68965d9bd..514d84f57c 100644
--- a/spec/controllers/embed_controller_spec.rb
+++ b/spec/controllers/embed_controller_spec.rb
@@ -11,14 +11,14 @@ describe EmbedController do
end
it "raises an error with a missing host" do
- SiteSetting.stubs(:embeddable_host).returns(nil)
+ SiteSetting.embeddable_hosts = nil
get :comments, embed_url: embed_url
expect(response).not_to be_success
end
context "with a host" do
before do
- SiteSetting.stubs(:embeddable_host).returns(host)
+ SiteSetting.embeddable_hosts = host
end
it "raises an error with no referer" do
@@ -27,7 +27,6 @@ describe EmbedController do
end
context "success" do
-
before do
controller.request.stubs(:referer).returns(embed_url)
end
@@ -51,8 +50,31 @@ describe EmbedController do
get :comments, embed_url: embed_url
end
end
-
end
+ context "with multiple hosts" do
+ before do
+ SiteSetting.embeddable_hosts = "#{host}\nhttp://discourse.org"
+ end
+ context "success" do
+ it "works with the first host" do
+ controller.request.stubs(:referer).returns("http://eviltrout.com/wat/1-2-3.html")
+ get :comments, embed_url: embed_url
+ expect(response).to be_success
+ end
+
+ it "works with the second host" do
+ controller.request.stubs(:referer).returns("https://discourse.org/blog-entry-1")
+ get :comments, embed_url: embed_url
+ expect(response).to be_success
+ end
+
+ it "doesn't work with a made up host" do
+ controller.request.stubs(:referer).returns("http://codinghorror.com/invalid-url")
+ get :comments, embed_url: embed_url
+ expect(response).to_not be_success
+ end
+ end
+ end
end
diff --git a/spec/models/site_setting_spec.rb b/spec/models/site_setting_spec.rb
index 0cd8691a66..a4ca870d5d 100644
--- a/spec/models/site_setting_spec.rb
+++ b/spec/models/site_setting_spec.rb
@@ -4,21 +4,34 @@ require_dependency 'site_setting_extension'
describe SiteSetting do
- describe "normalized_embeddable_host" do
- it 'returns the `embeddable_host` value' do
- SiteSetting.stubs(:embeddable_host).returns("eviltrout.com")
- expect(SiteSetting.normalized_embeddable_host).to eq("eviltrout.com")
+ describe "allows_embeddable_host" do
+ it 'works as expected' do
+ SiteSetting.embeddable_hosts = 'eviltrout.com'
+ expect(SiteSetting.allows_embeddable_host?('http://eviltrout.com')).to eq(true)
+ expect(SiteSetting.allows_embeddable_host?('https://eviltrout.com')).to eq(true)
+ expect(SiteSetting.allows_embeddable_host?('https://not-eviltrout.com')).to eq(false)
end
- it 'strip http from `embeddable_host` value' do
- SiteSetting.stubs(:embeddable_host).returns("http://eviltrout.com")
- expect(SiteSetting.normalized_embeddable_host).to eq("eviltrout.com")
+ it 'works with a http host' do
+ SiteSetting.embeddable_hosts = 'http://eviltrout.com'
+ expect(SiteSetting.allows_embeddable_host?('http://eviltrout.com')).to eq(true)
+ expect(SiteSetting.allows_embeddable_host?('https://eviltrout.com')).to eq(true)
+ expect(SiteSetting.allows_embeddable_host?('https://not-eviltrout.com')).to eq(false)
end
- it 'strip https from `embeddable_host` value' do
- SiteSetting.stubs(:embeddable_host).returns("https://eviltrout.com")
- expect(SiteSetting.normalized_embeddable_host).to eq("eviltrout.com")
+ it 'works with a https host' do
+ SiteSetting.embeddable_hosts = 'https://eviltrout.com'
+ expect(SiteSetting.allows_embeddable_host?('http://eviltrout.com')).to eq(true)
+ expect(SiteSetting.allows_embeddable_host?('https://eviltrout.com')).to eq(true)
+ expect(SiteSetting.allows_embeddable_host?('https://not-eviltrout.com')).to eq(false)
end
+
+ it 'works with multiple hosts' do
+ SiteSetting.embeddable_hosts = "https://eviltrout.com\nhttps://discourse.org"
+ expect(SiteSetting.allows_embeddable_host?('http://eviltrout.com')).to eq(true)
+ expect(SiteSetting.allows_embeddable_host?('http://discourse.org')).to eq(true)
+ end
+
end
describe 'topic_title_length' do
diff --git a/spec/models/topic_spec.rb b/spec/models/topic_spec.rb
index 673c7e9b69..ff971fa737 100644
--- a/spec/models/topic_spec.rb
+++ b/spec/models/topic_spec.rb
@@ -1400,8 +1400,8 @@ describe Topic do
let(:topic) { Fabricate.build(:topic) }
before do
- SiteSetting.stubs(:embeddable_host).returns("http://eviltrout.com")
- SiteSetting.stubs(:embed_truncate?).returns(true)
+ SiteSetting.embeddable_hosts = "http://eviltrout.com"
+ SiteSetting.embed_truncate = true
topic.stubs(:has_topic_embed?).returns(true)
end
@@ -1410,12 +1410,12 @@ describe Topic do
end
it "is false if embeddable_host is blank" do
- SiteSetting.stubs(:embeddable_host).returns(nil)
+ SiteSetting.embeddable_hosts = nil
expect(topic.expandable_first_post?).to eq(false)
end
it "is false if embed_truncate? is false" do
- SiteSetting.stubs(:embed_truncate?).returns(false)
+ SiteSetting.embed_truncate = false
expect(topic.expandable_first_post?).to eq(false)
end
diff --git a/test/javascripts/components/value-list-test.js.es6 b/test/javascripts/components/value-list-test.js.es6
new file mode 100644
index 0000000000..db8767471b
--- /dev/null
+++ b/test/javascripts/components/value-list-test.js.es6
@@ -0,0 +1,31 @@
+moduleForComponent('value-list', {integration: true});
+
+test('functionality', function(assert) {
+ andThen(() => {
+ this.render('{{value-list value=values}}');
+ });
+
+ andThen(() => {
+ assert.ok(this.$('.values .value').length === 0, 'it has no values');
+ assert.ok(this.$('input').length, 'it renders the input');
+ assert.ok(this.$('.btn-primary[disabled]').length, 'it is disabled with no value');
+ });
+
+ fillIn('input', 'eviltrout');
+ andThen(() => {
+ assert.ok(!this.$('.btn-primary[disabled]').length, "it isn't disabled anymore");
+ });
+
+ click('.btn-primary');
+ andThen(() => {
+ assert.ok(this.$('.values .value').length === 1, 'it adds the value');
+ assert.ok(this.$('input').val() === '', 'it clears the input');
+ assert.ok(this.$('.btn-primary[disabled]').length, "it is disabled again");
+ });
+
+ click('.value .btn-small');
+ andThen(() => {
+ assert.ok(this.$('.values .value').length === 0, 'it removes the value');
+ });
+
+});
From 90eca69e0a0574e458d6960e90b0eae083e431d3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?R=C3=A9gis=20Hanol?=
Date: Tue, 9 Jun 2015 19:31:14 +0200
Subject: [PATCH 25/60] FEATURE: add a link to the topic in the banner for
staff members
---
.../discourse/templates/components/discourse-banner.hbs | 3 +++
app/models/topic.rb | 3 ++-
config/locales/client.en.yml | 1 +
3 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/app/assets/javascripts/discourse/templates/components/discourse-banner.hbs b/app/assets/javascripts/discourse/templates/components/discourse-banner.hbs
index 39b419d6ff..1a647c5ea8 100644
--- a/app/assets/javascripts/discourse/templates/components/discourse-banner.hbs
+++ b/app/assets/javascripts/discourse/templates/components/discourse-banner.hbs
@@ -3,6 +3,9 @@
From d127e1179f39b977d6156b471208113170b19c12 Mon Sep 17 00:00:00 2001
From: Guo Xiang Tan
Date: Wed, 10 Jun 2015 14:39:29 +0800
Subject: [PATCH 32/60] FIX: Incorrect check when no text is selected.
---
.../javascripts/discourse/controllers/quote-button.js.es6 | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/assets/javascripts/discourse/controllers/quote-button.js.es6 b/app/assets/javascripts/discourse/controllers/quote-button.js.es6
index 0a4c0e3ada..c99f57247d 100644
--- a/app/assets/javascripts/discourse/controllers/quote-button.js.es6
+++ b/app/assets/javascripts/discourse/controllers/quote-button.js.es6
@@ -27,7 +27,7 @@ export default DiscourseController.extend({
const selection = window.getSelection();
// no selections
- if (selection.rangeCount === 0) return;
+ if (selection.isCollapsed) return;
// retrieve the selected range
const range = selection.getRangeAt(0),
From bf8c9c34110fdea5724067c4abc22e21b9f0235e Mon Sep 17 00:00:00 2001
From: Sam Saffron
Date: Wed, 10 Jun 2015 18:30:29 +1000
Subject: [PATCH 33/60] FEATURE: ship user_id with topic serializer
---
app/serializers/topic_view_serializer.rb | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/app/serializers/topic_view_serializer.rb b/app/serializers/topic_view_serializer.rb
index c2d98f077b..1fd56f54a1 100644
--- a/app/serializers/topic_view_serializer.rb
+++ b/app/serializers/topic_view_serializer.rb
@@ -32,7 +32,8 @@ class TopicViewSerializer < ApplicationSerializer
:category_id,
:word_count,
:deleted_at,
- :pending_posts_count
+ :pending_posts_count,
+ :user_id
attributes :draft,
:draft_key,
From c5b6ace07bd1c5195c2e34f2fda1ebcabff7abb2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?R=C3=A9gis=20Hanol?=
Date: Wed, 10 Jun 2015 10:32:02 +0200
Subject: [PATCH 34/60] update onebox to latest
---
Gemfile.lock | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Gemfile.lock b/Gemfile.lock
index e7b6d30551..e50f2484f3 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -208,7 +208,7 @@ GEM
omniauth-twitter (1.0.1)
multi_json (~> 1.3)
omniauth-oauth (~> 1.0)
- onebox (1.5.19)
+ onebox (1.5.20)
moneta (~> 0.7)
multi_json (~> 1.7)
mustache (~> 0.99)
From effe83d7a961b2b0334d9aec1905559c8015c01b Mon Sep 17 00:00:00 2001
From: Noam Yorav-Raphael
Date: Wed, 10 Jun 2015 11:47:07 +0300
Subject: [PATCH 35/60] Don't limit @mention autocomplete to latin characters
The userSearch() function, used for @mention autocomplete, returned an empty
list if the query string included non-latin characters or spaces. This removes
this restriction, so you can search users by any characters in their display
name, including spaces.
---
app/assets/javascripts/discourse/lib/user-search.js.es6 | 5 -----
1 file changed, 5 deletions(-)
diff --git a/app/assets/javascripts/discourse/lib/user-search.js.es6 b/app/assets/javascripts/discourse/lib/user-search.js.es6
index 790a00ff09..068282c75b 100644
--- a/app/assets/javascripts/discourse/lib/user-search.js.es6
+++ b/app/assets/javascripts/discourse/lib/user-search.js.es6
@@ -88,11 +88,6 @@ export default function userSearch(options) {
currentTerm = term;
return new Ember.RSVP.Promise(function(resolve) {
- // TODO site setting for allowed regex in username
- if (term.match(/[^a-zA-Z0-9_\.]/)) {
- resolve([]);
- return;
- }
if (((new Date() - cacheTime) > 30000) || (cacheTopicId !== topicId)) {
cache = {};
}
From 677cdbbda0f83dbbd5ea4dfbfaff3895168efc07 Mon Sep 17 00:00:00 2001
From: Jeff Atwood
Date: Wed, 10 Jun 2015 02:26:25 -0700
Subject: [PATCH 36/60] updated welcome usage tips and images
---
config/locales/server.en.yml | 42 +++++++++---------
public/images/welcome/notification-panel.png | Bin 3885 -> 3729 bytes
public/images/welcome/progress-bar.png | Bin 1528 -> 1083 bytes
public/images/welcome/username-completion.png | Bin 15028 -> 15521 bytes
4 files changed, 21 insertions(+), 21 deletions(-)
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index bb6ce41067..6ce907efab 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -1458,43 +1458,43 @@ en:
usage_tips:
text_body_template: |
- A few quick tips to get you started:
+ Here are a few quick tips to get you started:
- ## Keep scrolling
+ ## Just Scroll Down
- There are no next page buttons or page numbers – to read more, **just keep scrolling down!**
+ To read more, **just keep scrolling down!** As new posts or new topics arrive, they will appear automatically.
- As new posts come in, they will appear automatically.
+ ## Navigation
- ## Where am I?
+ - For search, your user page, or the ☰ menu, use the **icon buttons at upper right**.
- - For search, your user page, or the menu, use the **icon ☰ buttons at the upper right**.
+ - Selecting a topic title will always take you to the next unread post in the topic. To enter at the top or bottom instead, select the date or post count.
- - Any topic title will take you to the next unread post. Use the last activity time and post count to enter at the top or bottom.
-
- - While reading a topic, jump to the top ↑ by selecting the topic title. Select the green progress bar at the bottom right for full navigation controls, or use the home and end keys.
+ - While reading a topic, jump to the top ↑ by selecting the topic title. Select the progress bar at the bottom right for full navigation controls, or use the home and end keys.
- ## How do I reply?
+ ## Replying
- - To reply to the overall topic, use the Reply button at the very bottom of the page.
+ To reply …
- - To reply to a specific post, use the Reply button on that post.
+ - to the **topic in general**, use the Reply button at the very bottom of the topic.
- - To take the conversation in a different direction, but keep them linked together, use Reply as linked Topic to the right of the post.
+ - to a **specific person**, use the Reply button on their post.
- To quote someone in your reply, select the text you wish to quote, then press any Reply button.
+ - with **a linked topic**, use Reply as linked Topic to the right of the post.
+
+ To quote, simply select the text you wish to quote, then press any Reply button.
- To ping someone in your reply, mention their name. Type `@` and an autocompleter will pop up.
+ To ping someone in your reply, mention their name. Type `@` to begin selecting a name.
- For [standard Emoji](http://www.emoji.codes/), just start typing `:` or the traditional smileys `:)` :smile:
+ For [standard Emoji](http://www.emoji.codes/), just type `:` or the traditional smileys `:)` :smile:
- ## What else can I do?
+ ## Actions
There are action buttons at the bottom of each post.
@@ -1504,15 +1504,15 @@ en:
You can also **share** a link to a post, or **bookmark** it for later reference on your user page.
- ## Who is talking to me?
+ ## Notifications
When someone replies to your post, quotes your post, or mentions your `@username`, a number will immediately appear at the top right of the page. Use it access your **notifications**.
- Don't worry about missing a reply – you'll be emailed replies (and messages) if you aren't online when they arrive.
+ Don't worry about missing a reply – you'll be emailed notifications if you aren't online when they arrive.
- ## When are conversations new?
+ ## Your Preferences
By default all conversations less than two days old are considered new, and any conversation you've participated in (replied to, created, or read for an extended period) will automatically be tracked.
@@ -1522,7 +1522,7 @@ en:
You can change the individual notification state of a topic via the control at the bottom of the topic (this can also be set per category). To change how you track topics, or the definition of new, see [your user preferences](%{base_url}/my/preferences).
- ## Why can't I do certain things?
+ ## Community Trust
New users are somewhat limited for safety reasons. As you participate here, you'll gain the trust of the community, become a full citizen, and those limitations will automatically be removed. At a high enough [trust level](https://meta.discourse.org/t/what-do-user-trust-levels-do/4924), you'll gain even more abilities to help us manage our community together.
diff --git a/public/images/welcome/notification-panel.png b/public/images/welcome/notification-panel.png
index cd0550ec919a09d0ace1cb303454e42d2a6b623e..cbf6be4d8345740a2b93c373be0338a72af58f02 100644
GIT binary patch
literal 3729
zcmV;C4sP*@P)+-y7@uKzJ9`h2(0Y{4^JvsLM=8PtJdG=4=oSb{^xmQ0!
zk3Ja8&h{9C!5(9<#~2Lu7=ytc`$=S3N1{d4KaZNNsFgs23rMnr7Tt(MJ01-7*heDa
z0(p6i{8ENaOVOvp=wk``M1oEaqcc+UwH)1?Ajci#4z(GBJ@)v~loMr7l3?w}A>-6A
z;%G_ZD)R3roiW&BFNx~S
zBay&Dp4_N|w-b=U<;%$~moeC5?FypGiEYPO1?lu64E8vg6-9=I3}dPSv32M>S*Ukl
z*#~(&Ibg8I!i6wp@niK5R9P^Z+
diff --git a/test/javascripts/acceptance/topic-anonymous-test.js.es6 b/test/javascripts/acceptance/topic-anonymous-test.js.es6
index 2703ad0faa..8e5ddf45c2 100644
--- a/test/javascripts/acceptance/topic-anonymous-test.js.es6
+++ b/test/javascripts/acceptance/topic-anonymous-test.js.es6
@@ -16,6 +16,14 @@ test("Enter without an id", () => {
});
});
+test("Enter a 404 topic", (assert) => {
+ visit("/t/not-found/404");
+ andThen(() => {
+ assert.ok(!exists("#topic"), "The topic was not rendered");
+ assert.ok(find(".not-found").text() === "not found", "it renders the error message");
+ });
+});
+
test("Enter without access", (assert) => {
visit("/t/i-dont-have-access/403");
andThen(() => {
diff --git a/test/javascripts/helpers/create-pretender.js.es6 b/test/javascripts/helpers/create-pretender.js.es6
index 247f09b299..cad384f5e2 100644
--- a/test/javascripts/helpers/create-pretender.js.es6
+++ b/test/javascripts/helpers/create-pretender.js.es6
@@ -179,6 +179,10 @@ export default function() {
return response(403, {});
});
+ this.get('/t/404.json', () => {
+ return response(404, "not found");
+ });
+
this.get('/t/500.json', () => {
return response(502, {});
});
From 611b5f996e7a2b4a59ee820da0d1cf61d1552354 Mon Sep 17 00:00:00 2001
From: Neil Lalonde
Date: Wed, 10 Jun 2015 14:36:47 -0400
Subject: [PATCH 45/60] FIX: unpinned topics shouldn't remain pinned on
categories page
---
app/models/category_list.rb | 27 +++++++++++++++++++++
lib/topic_query.rb | 3 ++-
spec/components/category_list_spec.rb | 21 ++++++++++++++++
spec/models/category_featured_topic_spec.rb | 6 +++--
4 files changed, 54 insertions(+), 3 deletions(-)
diff --git a/app/models/category_list.rb b/app/models/category_list.rb
index b7868cdb0f..b5cae822b8 100644
--- a/app/models/category_list.rb
+++ b/app/models/category_list.rb
@@ -1,3 +1,5 @@
+require_dependency 'pinned_check'
+
class CategoryList
include ActiveModel::Serialization
@@ -17,6 +19,8 @@ class CategoryList
prune_empty
find_user_data
+ sort_unpinned
+ trim_results
end
private
@@ -151,4 +155,27 @@ class CategoryList
@all_topics.each { |ft| ft.user_data = topic_lookup[ft.id] }
end
end
+
+ def sort_unpinned
+ if @guardian.current_user && @all_topics.present?
+ # Put unpinned topics at the end of the list
+ @categories.each do |c|
+ next if c.displayable_topics.blank? || c.displayable_topics.size <= latest_posts_count
+ unpinned = []
+ c.displayable_topics.each do |t|
+ unpinned << t if t.pinned_at && PinnedCheck.unpinned?(t, t.user_data)
+ end
+ unless unpinned.empty?
+ c.displayable_topics = (c.displayable_topics - unpinned) + unpinned
+ end
+ end
+ end
+ end
+
+ def trim_results
+ @categories.each do |c|
+ next if c.displayable_topics.blank?
+ c.displayable_topics = c.displayable_topics[0,latest_posts_count]
+ end
+ end
end
diff --git a/lib/topic_query.rb b/lib/topic_query.rb
index 15af3dd515..13c3f9bb4b 100644
--- a/lib/topic_query.rb
+++ b/lib/topic_query.rb
@@ -135,9 +135,10 @@ class TopicQuery
def list_category_topic_ids(category)
query = default_results(category: category.id)
pinned_ids = query.where('pinned_at IS NOT NULL AND category_id = ?', category.id)
+ .limit(nil)
.order('pinned_at DESC').pluck(:id)
non_pinned_ids = query.where('pinned_at IS NULL OR category_id <> ?', category.id).pluck(:id)
- (pinned_ids + non_pinned_ids)[0...@options[:per_page]]
+ (pinned_ids + non_pinned_ids)
end
def list_new_in_category(category)
diff --git a/spec/components/category_list_spec.rb b/spec/components/category_list_spec.rb
index 104173d3b9..808abcd5d8 100644
--- a/spec/components/category_list_spec.rb
+++ b/spec/components/category_list_spec.rb
@@ -102,6 +102,27 @@ describe CategoryList do
end
end
+ context "with pinned topics in a category" do
+ let!(:topic1) { Fabricate(:topic, category: topic_category, bumped_at: 8.minutes.ago) }
+ let!(:topic2) { Fabricate(:topic, category: topic_category, bumped_at: 5.minutes.ago) }
+ let!(:topic3) { Fabricate(:topic, category: topic_category, bumped_at: 2.minutes.ago) }
+ let!(:pinned) { Fabricate(:topic, category: topic_category, pinned_at: 10.minutes.ago, bumped_at: 10.minutes.ago) }
+ let(:category) { category_list.categories.first }
+
+ before do
+ SiteSetting.stubs(:category_featured_topics).returns(2)
+ end
+
+ it "returns pinned topic first" do
+ expect(category.displayable_topics.map(&:id)).to eq([pinned.id, topic3.id])
+ end
+
+ it "returns topics in bumped_at order if pinned was unpinned" do
+ PinnedCheck.stubs(:unpinned?).returns(true)
+ expect(category.displayable_topics.map(&:id)).to eq([topic3.id, topic2.id])
+ end
+ end
+
end
describe 'category order' do
diff --git a/spec/models/category_featured_topic_spec.rb b/spec/models/category_featured_topic_spec.rb
index 4859ea7cde..38c3383a45 100644
--- a/spec/models/category_featured_topic_spec.rb
+++ b/spec/models/category_featured_topic_spec.rb
@@ -34,9 +34,11 @@ describe CategoryFeaturedTopic do
it 'should feature stuff in the correct order' do
+ SiteSetting.stubs(:category_featured_topics).returns(3)
category = Fabricate(:category)
- _t3 = Fabricate(:topic, category_id: category.id, bumped_at: 7.minutes.ago)
+ t4 = Fabricate(:topic, category_id: category.id, bumped_at: 10.minutes.ago)
+ t3 = Fabricate(:topic, category_id: category.id, bumped_at: 7.minutes.ago)
t2 = Fabricate(:topic, category_id: category.id, bumped_at: 4.minutes.ago)
t1 = Fabricate(:topic, category_id: category.id, bumped_at: 5.minutes.ago)
pinned = Fabricate(:topic, category_id: category.id, pinned_at: 10.minutes.ago, bumped_at: 10.minutes.ago)
@@ -45,7 +47,7 @@ describe CategoryFeaturedTopic do
expect(
CategoryFeaturedTopic.where(category_id: category.id).pluck(:topic_id)
- ).to eq([pinned.id, t2.id, t1.id])
+ ).to eq([pinned.id, t2.id, t1.id, t3.id])
end
end
From ae52f4e776c3a466a96ba0f67edb39e8cd37b1c2 Mon Sep 17 00:00:00 2001
From: Jeff Atwood
Date: Wed, 10 Jun 2015 12:53:57 -0700
Subject: [PATCH 46/60] Revert "Don't limit @mention autocomplete to latin
characters"
This reverts commit effe83d7a961b2b0334d9aec1905559c8015c01b.
---
app/assets/javascripts/discourse/lib/user-search.js.es6 | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/app/assets/javascripts/discourse/lib/user-search.js.es6 b/app/assets/javascripts/discourse/lib/user-search.js.es6
index 068282c75b..790a00ff09 100644
--- a/app/assets/javascripts/discourse/lib/user-search.js.es6
+++ b/app/assets/javascripts/discourse/lib/user-search.js.es6
@@ -88,6 +88,11 @@ export default function userSearch(options) {
currentTerm = term;
return new Ember.RSVP.Promise(function(resolve) {
+ // TODO site setting for allowed regex in username
+ if (term.match(/[^a-zA-Z0-9_\.]/)) {
+ resolve([]);
+ return;
+ }
if (((new Date() - cacheTime) > 30000) || (cacheTopicId !== topicId)) {
cache = {};
}
From 857ff3515de6efd05f4dd892ad9f11615923a3a6 Mon Sep 17 00:00:00 2001
From: Jeff Atwood
Date: Wed, 10 Jun 2015 16:14:51 -0700
Subject: [PATCH 47/60] minor copyedit
---
config/locales/client.en.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 8e1e1b142e..c82164d1fa 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -1131,8 +1131,8 @@ en:
sso_enabled: "Enter the username of the person you'd like to invite to this topic."
to_topic_blank: "Enter the username or email address of the person you'd like to invite to this topic."
to_topic_email: "You've entered an email address. We'll email an invitation that allows your friend to immediately reply to this topic."
- to_topic_username: "You've entered a username. We'll send a notification to that user with a link inviting them to this topic."
- to_username: "Enter the username of the person you'd like to invite. We'll send a notification to that user with a link inviting them to this topic."
+ to_topic_username: "You've entered a username. We'll send a notification with a link inviting them to this topic."
+ to_username: "Enter the username of the person you'd like to invite. We'll send a notification with a link inviting them to this topic."
email_placeholder: 'name@example.com'
success_email: "We mailed out an invitation to {{emailOrUsername}}. We'll notify you when the invitation is redeemed. Check the invitations tab on your user page to keep track of your invites."
From a14ea757a123ca9f89797d9bd6dd1b50556b9413 Mon Sep 17 00:00:00 2001
From: Jeff Atwood
Date: Wed, 10 Jun 2015 17:07:29 -0700
Subject: [PATCH 48/60] improvements to new user welcome copy
---
config/locales/server.en.yml | 32 ++++++++++--------
public/images/welcome/progress-bar.png | Bin 1083 -> 5388 bytes
.../welcome/topic-list-select-areas.png | Bin 0 -> 8662 bytes
.../welcome/topic-notification-control.png | Bin 0 -> 39580 bytes
4 files changed, 18 insertions(+), 14 deletions(-)
create mode 100644 public/images/welcome/topic-list-select-areas.png
create mode 100644 public/images/welcome/topic-notification-control.png
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index 6ce907efab..2f7854c1a0 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -1460,39 +1460,39 @@ en:
text_body_template: |
Here are a few quick tips to get you started:
- ## Just Scroll Down
+ ## Reading
- To read more, **just keep scrolling down!** As new posts or new topics arrive, they will appear automatically.
+ To read more, **just keep scrolling down!** As new posts or new topics arrive, they will appear automatically – no need to refresh the page.
## Navigation
- For search, your user page, or the ☰ menu, use the **icon buttons at upper right**.
- - Selecting a topic title will always take you to the next unread post in the topic. To enter at the top or bottom instead, select the date or post count.
+ - Selecting a topic title will always take you to the next unread post in the topic. To enter at the top or bottom instead, select the date or post count instead.
+
+
- While reading a topic, jump to the top ↑ by selecting the topic title. Select the progress bar at the bottom right for full navigation controls, or use the home and end keys.
-
+
## Replying
- To reply …
+ - to the **topic in general**, use Reply at the very bottom of the topic.
- - to the **topic in general**, use the Reply button at the very bottom of the topic.
-
- - to a **specific person**, use the Reply button on their post.
+ - to a **specific person**, use Reply on their post.
- with **a linked topic**, use Reply as linked Topic to the right of the post.
- To quote, simply select the text you wish to quote, then press any Reply button.
+ To insert a quote, simply select the text you wish to quote, then press any Reply button.
- To ping someone in your reply, mention their name. Type `@` to begin selecting a name.
+ To notify someone in your reply, mention their name. Type `@` to begin selecting a name.
- For [standard Emoji](http://www.emoji.codes/), just type `:` or the traditional smileys `:)` :smile:
+ To use [standard Emoji](http://www.emoji.codes/), just type `:` or the traditional smileys `:)` :smile:
## Actions
@@ -1510,17 +1510,21 @@ en:
- Don't worry about missing a reply – you'll be emailed notifications if you aren't online when they arrive.
+ Don't worry about missing a reply – you'll be emailed notifications that arrive when you are away.
## Your Preferences
- By default all conversations less than two days old are considered new, and any conversation you've participated in (replied to, created, or read for an extended period) will automatically be tracked.
+ By default all conversations less than two days old are considered new, and any conversation you've actively participated in (by replying, creating the topic, or reading for an extended period) will automatically be tracked.
You will see the blue new and number indicators next to these topics:
- You can change the individual notification state of a topic via the control at the bottom of the topic (this can also be set per category). To change how you track topics, or the definition of new, see [your user preferences](%{base_url}/my/preferences).
+ You can change the individual notification state of a topic via the notification control at the bottom of the topic. You can also set notification state per category.
+
+
+
+ To change these settings, see [your user preferences](%{base_url}/my/preferences).
## Community Trust
diff --git a/public/images/welcome/progress-bar.png b/public/images/welcome/progress-bar.png
index aeff46a2f8ff580a764949dbe00fafe9df20795a..7545a9b6cc72433b0bdb7f3772bad13ce8e4d6f0 100644
GIT binary patch
literal 5388
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
diff --git a/public/images/welcome/topic-list-select-areas.png b/public/images/welcome/topic-list-select-areas.png
new file mode 100644
index 0000000000000000000000000000000000000000..0652d30446b873995558cd3cb0c24486d8b49edd
GIT binary patch
literal 8662
zcmajFcU%)$6gC_LML@wNG-)=f2vO-t00j}L0clDJ7JBc5js+1|f`D`r+CnJOqzQy3
zLMQ?Yp?3%^lwhdR-(lVTzPJ5;@BA@y=FT(s+&ib-ne$Azu8!J8Ms7wB2y{{X(Syez
z5M2f!d!0W6>@TZYnSemoFzOHP8=#PD7=$D5P$0ph?OIrRTSgeKyT~a2j&6wP2X0Y&
znzI_|G${2@p{NwLYkirXCQNR+nI(d1G=q7LqxX69lx5DXNM8-T!*(azbRQw`M#75e
zsc_|BjALs?U4(#s=6tq@ooIwvOf1gAJNGCa^+{=bWW-!T=?eV3z?8paS#(){(8BKI
zLEzl#NEuQM7~JWWIzas`fC#kz2#x>SIf$3#H^3GC`^dZg2L10XP{P09bQxpt@3tq=
z^uGl;{@=%G)bC>miRK@{tZqKgf3SmOY-;L&f4co}p{`|2Zqn~J2;YE5YhcVb6d!-t
z@Lx1BbvSIM7c;z~RJPF)DT|+Hal>={1MH1e@tAyRT=y+A@EzsRRiCbd?=Du_jA*Ab
z1pQwWpWxb@SI+&mzejx4WMf3su<)FgqK})^YqKyKm#HmE{j)Dyd1aH^V@?zlYp-<0
z6W?F3iuN=$y`ebh(e6g8{{z0~8_zoGCXCJjL+Xt+=J+(KL&uh3zH+8UehkWW8w%;tcl6zUt+oJ$ArF-P%18iu)@BBQ
zE{Xg8>PQ)#WWnpu&7Ij0JP_|JmO0DXtKz{pD?0tDpDHu)PtP2Lg2ia*Q+)3-%<1dK
z9(myJP^k)inhCDtO)(w=Z%}NpPa>-vGWFli2V`t_Ee0p`Kc|C0S50DFi|p23uQhOu
zl?BDo?kh-4PL3mYzg-Mmcse4ulqGnS+DIMUyLPdn|FLEvo+4RrkU?fT<~ax4+IM@3
z7-rXH6!&@gjr2X9@i>P=%@pAijcJtA-iLFb=@y>jE7!%2Bmom&5?6n8@+0NhyUO}u
z&&HEmpBX?)W6aZWHeJ+JvPuBK;Ur871d^;e6#nec!D9$HbpmJmFWQ_vAm0gYzP>8H
z#>7DZ1KnKtSEPzH6>bn{&G$$zwfx>P;BJ|M7a3ITlXtmQbo&|-XyuTU6b9@IKh_~S
z^iU26G+a4k!hSfK33%KCP3~`jk~<*1TIc1M7o9Y`>l1>Z8DXjl2jiI4V-DhhgPsz`jC<}>Czecugn9YJ7%$IIpU37NymzV+%
z=Hid?{X9x@0o2c#KC|t^U~xRA0Dri9)0j|?+Ec1E*b1%3h>hd&Q;CqgYW$P87#K(iz=Ug4p}$<*D{CTaR<3H7)n2hUVv#qUP@VAlkTWu|J=7p*
zQ&5}hKbFET@s{LfGmCQi{0#)MNi2Uf98o{P4IKSC!1EN%H;Ge*W`b6LFQ>*uAh
zB#5GSen=}YN6G#_(pss8RES!0i&6mAi;U7=)T_}2pYFQoaOJy$Nq
zmbz%2U^?nJPh`FX9L-MU=DKNh5!LHzo&@ab-japIQZ-ei5W?G)x8MqNSCxRMMANTk
zUcQcUnb_qlBLw?xSG>Fv$!+ngx7lU=xIiukr$1C0Y5n0{`#8U-lB+v)(XC^1C@d}M
zsK~EHRsmCkHkTp|PmC8#;eBUpx9jwd{I^?rWWR5D+Pg4)BLap2mIMNA!i;Fa{jVf(
zysU3oK*s*@NRn#Q!2FvlRAbgQaEqb~Gla>ujs$!`Iskm`Xf-O=8NE(75Y1u#@k!
z*Hp`-1|Fv1T7$L_z4$g=tVDBU(`w;m)xH-{?D4kE%bQw@9EPH=aWX+K?XcD^`)cnm
z%#-ipxyI|=^kT{fp;(D~iLaQRpAR7k{%VX