From 6004ca22bbbb10c2ae7d036abce9d8ddd8ed21b8 Mon Sep 17 00:00:00 2001 From: weeklies <80141759+weeklies@users.noreply.github.com> Date: Fri, 17 May 2024 17:18:51 +0100 Subject: [PATCH 001/181] AO3-5371 Add descriptive classes to external work and series blurbs (#4560) * Add descriptive classes to series and external work blurbs * Try to do i18n * AO3-5371 Add number delimiter * AO3-5371 Normalize * AO3-5371 Move localization to model * AO3-5371 Missed one * AO3-5371 Fix typo --- app/helpers/bookmarks_helper.rb | 2 +- app/views/external_works/_blurb.html.erb | 8 ++++---- app/views/external_works/_work_module.html.erb | 10 +++++----- app/views/series/_series_module.html.erb | 12 ++++++------ app/views/series/show.html.erb | 12 ++++++------ config/locales/models/en.yml | 12 ++++++++++-- 6 files changed, 32 insertions(+), 24 deletions(-) diff --git a/app/helpers/bookmarks_helper.rb b/app/helpers/bookmarks_helper.rb index b1d7be1e0f5..a9c09e3b2f4 100644 --- a/app/helpers/bookmarks_helper.rb +++ b/app/helpers/bookmarks_helper.rb @@ -31,7 +31,7 @@ def link_to_tag_bookmarks(tag) def link_to_bookmarkable_bookmarks(bookmarkable, link_text='') if link_text.blank? - link_text = Bookmark.count_visible_bookmarks(bookmarkable, current_user) + link_text = number_with_delimiter(Bookmark.count_visible_bookmarks(bookmarkable, current_user)) end path = case bookmarkable.class.name when "Work" diff --git a/app/views/external_works/_blurb.html.erb b/app/views/external_works/_blurb.html.erb index 1a85e33a4e6..e9b33ee4820 100644 --- a/app/views/external_works/_blurb.html.erb +++ b/app/views/external_works/_blurb.html.erb @@ -42,12 +42,12 @@
<%= external_work.language.name %>
<% end %> <% if Bookmark.count_visible_bookmarks(external_work) > 0 %> -
<%= ts("Bookmarks") + ": " %>
-
<%= link_to_bookmarkable_bookmarks(external_work) %>
+
<%= Bookmark.model_name.human(count: :many) %>:
+
<%= link_to_bookmarkable_bookmarks(external_work) %>
<% end %> -
<%= ts("Related Works") + ": "%>
-
<%= link_to external_work.related_works.count.to_s, external_work %>
+
<%= RelatedWork.model_name.human(count: :many) %>:
+
<%= link_to number_with_delimiter(external_work.related_works.count), external_work %>
<% if policy(external_work).show_admin_options? %> diff --git a/app/views/external_works/_work_module.html.erb b/app/views/external_works/_work_module.html.erb index 52c3de7a505..28fc39142d4 100644 --- a/app/views/external_works/_work_module.html.erb +++ b/app/views/external_works/_work_module.html.erb @@ -19,7 +19,7 @@

- <%= ts("This work isn't hosted on the Archive so this blurb might not be complete + <%= ts("This work isn't hosted on the Archive so this blurb might not be complete or accurate.") %>

@@ -49,12 +49,12 @@ <% end %> <% if Bookmark.count_visible_bookmarks(external_work) > 0 %> -
<%= ts("Bookmarks:") %>
-
<%= link_to_bookmarkable_bookmarks(external_work) %>
+
<%= Bookmark.model_name.human(count: :many) %>:
+
<%= link_to_bookmarkable_bookmarks(external_work) %>
<% end %> <% if external_work.related_works.count > 0 %> -
<%= ts("Related Works:") %>
-
<%= link_to external_work.related_works.count.to_s, external_work %>
+
<%= RelatedWork.model_name.human(count: :many) %>:
+
<%= link_to number_with_delimiter(external_work.related_works.count), external_work %>
<% end %> diff --git a/app/views/series/_series_module.html.erb b/app/views/series/_series_module.html.erb index a7889f87720..c2a0414604f 100644 --- a/app/views/series/_series_module.html.erb +++ b/app/views/series/_series_module.html.erb @@ -41,12 +41,12 @@ <% end %>
-
<%= ts('Words:') %>
-
<%= number_with_delimiter(series.visible_word_count) %>
-
<%= ts('Works:') %>
-
<%= number_with_delimiter(series.visible_work_count) %>
+
<%= Work.human_attribute_name("word_count") %>:
+
<%= number_with_delimiter(series.visible_word_count) %>
+
<%= Work.model_name.human(count: :many) %>:
+
<%= number_with_delimiter(series.visible_work_count) %>
<% if (bookmark_count = series.bookmarks.is_public.count) > 0 %> -
<%= ts('Bookmarks:') %>
-
<%= link_to_bookmarkable_bookmarks(series, number_with_delimiter(bookmark_count)) %>
+
<%= Bookmark.model_name.human(count: :many) %>:
+
<%= link_to_bookmarkable_bookmarks(series, number_with_delimiter(bookmark_count)) %>
<% end %>
diff --git a/app/views/series/show.html.erb b/app/views/series/show.html.erb index 4d87bbf92ff..029c3731d80 100644 --- a/app/views/series/show.html.erb +++ b/app/views/series/show.html.erb @@ -58,15 +58,15 @@
<%= ts("Stats:") %>
-
<%= ts("Words:") %>
-
<%= number_with_delimiter(@series.visible_word_count) %>
-
<%= ts("Works:") %>
-
<%= number_with_delimiter(@series.visible_work_count) %>
+
<%= Work.human_attribute_name("word_count") %>:
+
<%= number_with_delimiter(@series.visible_word_count) %>
+
<%= Work.model_name.human(count: :many) %>:
+
<%= number_with_delimiter(@series.visible_work_count) %>
<%= ts("Complete:") %>
<%= @series.complete? ? ts("Yes") : ts("No") %>
<% if (bookmark_count = @series.bookmarks.is_public.count) > 0 %> -
<%= ts("Bookmarks:") %>
-
<%= link_to_bookmarkable_bookmarks(@series, number_with_delimiter(bookmark_count)) %>
+
<%= Bookmark.model_name.human(count: :many) %>:
+
<%= link_to_bookmarkable_bookmarks(@series, number_with_delimiter(bookmark_count)) %>
<% end %>
diff --git a/config/locales/models/en.yml b/config/locales/models/en.yml index 61e3df5bb02..49f2435dcb3 100644 --- a/config/locales/models/en.yml +++ b/config/locales/models/en.yml @@ -79,6 +79,7 @@ en: chapter_total_display: Chapters summary: Summary user_defined_tags_count: Fandom, relationship, character, and additional tags + word_count: Words work/chapters: base: 'Invalid chapter:' content: Content @@ -188,7 +189,9 @@ en: archive_warning: one: Warning other: Warnings - bookmark: Bookmark + bookmark: + one: Bookmark + other: Bookmarks category: one: Category other: Categories @@ -209,6 +212,9 @@ en: rating: one: Rating other: Ratings + related_work: + one: Related Work + other: Related Works relationship: one: Relationship other: Relationships @@ -218,7 +224,9 @@ en: tag: one: Tag other: Tags - work: Work + work: + one: Work + other: Works attributes: ticket_number: Ticket ID errors: From 4b56f1c61f5e576897b9bee3c652403c27e51ffc Mon Sep 17 00:00:00 2001 From: sarken Date: Fri, 17 May 2024 12:19:05 -0400 Subject: [PATCH 002/181] AO3-6720 Use Latin-script Serbian locale instead of Cyrillic (#4797) --- .phrase.yml | 6 +- config/application.rb | 4 +- config/locales/phrase-exports/sr.yml | 372 --------------------------- 3 files changed, 5 insertions(+), 377 deletions(-) delete mode 100644 config/locales/phrase-exports/sr.yml diff --git a/.phrase.yml b/.phrase.yml index c4cd012f7ed..cc7e9347e5b 100644 --- a/.phrase.yml +++ b/.phrase.yml @@ -134,15 +134,15 @@ phrase: - file: ./config/locales/phrase-exports/ru.yml params: locale_id: ru + - file: ./config/locales/phrase-exports/scr.yml + params: + locale_id: scr - file: ./config/locales/phrase-exports/sk.yml params: locale_id: sk - file: ./config/locales/phrase-exports/sl.yml params: locale_id: sl - - file: ./config/locales/phrase-exports/sr.yml - params: - locale_id: sr - file: ./config/locales/phrase-exports/sv.yml params: locale_id: sv diff --git a/config/application.rb b/config/application.rb index 0dc4bf8a2d1..992db9c0a25 100644 --- a/config/application.rb +++ b/config/application.rb @@ -40,8 +40,8 @@ class Application < Rails::Application I18n.config.available_locales = [ :en, :af, :ar, :bg, :bn, :ca, :cs, :cy, :da, :de, :el, :es, :fa, :fi, :fil, :fr, :he, :hi, :hr, :hu, :id, :it, :ja, :ko, :lt, :lv, :mk, - :mr, :ms, :nb, :nl, :pl, :"pt-BR", :"pt-PT", :ro, :ru, :sk, :sl, :sr, :sv, - :th, :tr, :uk, :vi, :"zh-CN" + :mr, :ms, :nb, :nl, :pl, :"pt-BR", :"pt-PT", :ro, :ru, :scr, :sk, :sl, + :sv, :th, :tr, :uk, :vi, :"zh-CN" ] # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. diff --git a/config/locales/phrase-exports/sr.yml b/config/locales/phrase-exports/sr.yml deleted file mode 100644 index ae848ebddc0..00000000000 --- a/config/locales/phrase-exports/sr.yml +++ /dev/null @@ -1,372 +0,0 @@ ---- -sr: - activerecord: - attributes: - archive_warning: - name_with_colon: - few: 'Упозорења:' - one: 'Упозорење:' - other: 'Упозорења:' - category: - name_with_colon: - few: 'Категорије:' - one: 'Категорија:' - other: 'Категорије:' - character: - name_with_colon: - few: 'Ликови:' - one: 'Лик:' - other: 'Ликови:' - fandom: - name_with_colon: - few: 'Фандоми:' - one: 'Фандом:' - other: 'Фандоми:' - freeform: - name_with_colon: - few: 'Додатни тагови:' - one: 'Додатан таг:' - other: 'Додатни тагови:' - rating: - name_with_colon: 'Оцена примерености садржаја:' - relationship: - name_with_colon: - few: 'Односи између ликова:' - one: 'Однос између ликова:' - other: 'Односи између ликова:' - work: - summary: Сажетак - models: - archive_warning: - few: Упозорења - one: Упозорење - other: Упозорења - category: - few: Категорије - one: Категорија - other: Категорије - chapter: - few: Главе - one: Глава - other: Главе - character: - few: Ликови - one: Лик - other: Ликови - fandom: - few: Фандоми - one: Фандом - other: Фандоми - freeform: - few: Додатни тагови - one: Додатан таг - other: Додатни тагови - rating: - few: Оцене примерености садржаја - one: Оцена примерености садржаја - other: Оцене примерености садржаја - relationship: - few: Односи између ликова - one: Однос између ликова - other: Односи између ликова - series: - few: Серијали - one: Серијал - other: Серијали - kudo_mailer: - batch_kudo_notification: - guest: - few: "%{count} госта" - one: "%{count} гост" - other: "%{count} гостију" - html: - left_kudos: - few: "%{givers_list} су похвалили %{commentable_link}." - one: "%{givers_list} је похвалио/ла %{commentable_link}." - other: "%{givers_list} су похвалили %{commentable_link}." - single_guest: - giver_list: Гост/гошћа - left_kudos: "%{giver_list} је оставио/ла похвалу на %{commentable_link}." - subject: "[%{app_name}] Добили сте похвале!" - text: - left_kudos: - few: "%{givers_list} су похвалили %{commentable_title} (%{commentable_url})." - one: "%{givers_list} је похвалио/ла %{commentable_title} (%{commentable_url})." - other: "%{givers_list} су похвалили %{commentable_title} (%{commentable_url})." - single_guest: Гост/гошћа је оставио/ла похвалу на %{commentable_title} (%{commentable_url}). - mailer: - general: - closing: - formal: Срдачан поздрав, - informal: Све најбоље, - creation: - link_with_word_count: "%{creation_link} (%{word_count})" - title_with_chapter_number: "%{title}: Поглавље %{position}" - title_with_word_count: '"%{creation_title}" (%{word_count})' - word_count: - few: "%{count} речи" - one: "%{count} реч" - other: "%{count} речи" - footer: - general: - about: - html: AO3 је архива коју воде и подржавају фанови, а која се ослања на %{donate_link}. - text: 'АО3 је архива коју воде и подржавају фанови, а која се ослања на Ваше донације: %{donate_url}.' - html: - donate_link_text: Ваше донације - support_link_text: контактирајте Корисничку подршку - unwanted_email: - html: Ако сте грешком примили ову поруку, молимо Вас %{support_link}. - text: Ако сте грешком примили ову поруку, контактирајте Корисничку подршку на %{support_url}. - sent_at: Послато у %{sent_at}. - greeting: - formal: Поштовани/а %{name}, - informal: - addressed: Здраво, %{name}! - unaddressed: Здраво! - introductory: Здраво од Archive of Our Own – AO3-а (Наше сопствене архиве)! - metadata_label_indicator: ": " - signature: - app_short_name: AO3 - open_doors: Тим Open Doors (Отворених врата) - parent_org: Organization for Transformative Works – OTW (Организација за трансформативне радове) - support: Тим Корисничке подршке AO3-а - user_mailer: - abuse_report: - description_para: 'Опис злоупотребе:' - para1: 'Пријава злоупотребе која следи је послата Тиму за Злоупотребе:' - subject: "[%{app_name}] Ваша Пријава Злоупотребе" - url_para: 'УРЛ странице на којој се налази злоупотреба:' - admin_deleted_work_notification: - bye: У прилогу је копија Вашег дела за Вашу референцу. - contact_abuse: контактирајте наш Одбор за злоупотребу - deleted: - html: Ваше дело %{title} је избрисано са АО3-a од стране администратора сајта. - text: Ваше дело "%{title}" је избрисано са AO3-a од стране администратора сајта. - html: - tos_violation: Ако постоји могућност да је Ваше дело прекршило Услове коришћења AO3-a, молимо Вас, %{contact_abuse_link}. - import_project: - html: Ако је Ваше дело било део увезеног пројекта којим се бави наш тим Open Doors (Отворених врата), молимо Вас, %{opendoors_link} ако имате додатна питања. - text: Ако је Ваше дело било део увезеног пројекта којим се бави наш тим Open Doors (Отворених врата), молимо Вас, контактирајте Отворена врата (%{opendoors_link}) ако имате додатна питања. - opendoors: контактирајте Отворена врата - subject: "[%{app_name}] Ваше дело је избрисано од стране администратора" - text: - tos_violation: Ако постоји могућност да је Ваше дело прекршило Услове коришћења АО3-а, молимо Вас, контактирајте наш Одбор за злоупотребу (%{contact_abuse_url}). - admin_hidden_work_notification: - access: Иако је Ваше дело сакривено, и даље ћете моћи да му приступите кроз линк изнад, али неће бити наведено на Вашој страници дела, и неће бити доступно другим корисницима АО3-а. - check_email: Молимо проверите Ваш имејл, укључујући фолдер за спам, јер Вас је тим Политике и злоупотребе можда већ контактирао и објаснио зашто је Ваше дело сакривено. - contact_abuse: контактирајте Политику и злоупотребу - html: - help: Ако нисте сигурни зашто је Ваше дело сакривено, и нисте примили више информација о овоме, молимо директно %{contact_abuse_link}. - hidden: Ваше дело %{title} је сакривено од стране тима Политике и злоупотребе и више није јавно доступно. - tos_violation: Ако је Ваше дело сакривено јер је прекршило АО3-ове %{tos_link}, мораћете да исправите прекршај. Ако Ваше дело не доведете у сагласност са Условима коришћења, то може довести до брисања Вашег дела са АО3-а. - subject: "[%{app_name}] Ваше дело је сакривено од стране тима Политике и злоупотребе" - text: - help: 'Ако нисте сигурни зашто је Ваше дело сакривено, и нисте примили више информација о овоме, молимо директно контактирајте Политику и злоупотребу: %{contact_abuse_url}.' - hidden: Ваше дело “%{title}” (%{work_url}) је сакривено од стране тима Политике и злоупотребе и више није јавно доступно. - tos_violation: Ако је Ваше дело сакривено јер је прекршило АО3-ове Услове коришћења (%{tos_url}), мораћете да исправите прекршај. Ако Ваше дело не доведете у сагласност са Условима коришћења, то може довести до брисања Вашег дела са АО3-а. - tos: Услове коришћења - anonymous_or_unrevealed_notification: - anonymous_info: Анонимна дела су укључена у излистане тагове, али нису на Вашој страници радова. На самом делу, Ваше корисничко име ће бити замењено са "Anonymous" (Анонимно). - anonymous_unrevealed_info: Одржаваоци збирке могу касније открити Ваше дело, али га оставити анонимним. Људи који Вас буду пратили неће бити обавештени када ова промена ступи на снагу. Након што је откривено, Ваше дело ће бити укључено у излистане тагове, али неће бити на страници Ваших радова. На самом делу, Ваше корисничко име ће бити замењено са "Anonymous" (Анонимно). - changed_status: - anonymous: - html: Одржаваоци %{collection_link} збирке су променили статус Вашег дела %{work_link} на анонимно. - text: Одржаваоци "%{collection_title}" (%{collection_url}) збирке су променили статус Вашег дела "%{work_title}" (%{work_url}) на анонимно. - anonymous_unrevealed: - html: Одржаваоци %{collection_link} збирке су променили статус Вашег дела %{work_link} на анонимно и неоткривено. - text: Одржаваоци "%{collection_title}" (%{collection_url}) збирке су променили статус Вашег дела "%{work_title}" (%{work_url}) на анонимно и неоткривено. - unrevealed: - html: Одржаваоци %{collection_link} збирке су променили статус Вашег дела %{work_link} на неоткривено. - text: Одржаваоци "%{collection_title}" (%{collection_url}) збирке су променили статус Вашег дела "%{work_title}" (%{work_url}) на неоткривено. - collection_items_link_text: страницу Approved Collection Items (Одобрени предмети из збирке) - do_not_want: - anonymous: - html: Ако не желите да Ваше дело буде анонимно, молимо Вас да посетите Вашу %{collection_items_link} како бисте га уклонили из ове збирке. - text: 'Ако не желите да Ваше дело буде анонимно, молимо Вас да посетите Вашу страницу Approved Collection Items (Одобрени предмети из збирке) како бисте га уклонили из ове збирке: %{collection_items_url}' - anonymous_unrevealed: - html: Ако не желите да Ваше дело буде анонимно и неоткривено, молимо Вас да посетите Вашу %{collection_items_link} како бисте га уклонили из ове збирке. - text: 'Ако не желите да Ваше дело буде анонимно и неоткривено, молимо Вас да посетите Вашу страницу Approved Collection Items (Одобрени предмети из збирке) како бисте га уклонили из ове збирке: %{collection_items_url}' - unrevealed: - html: Ако не желите да Ваше дело буде неоткривено, молимо Вас да посетите Вашу %{collection_items_link} како бисте га уклонили из ове збирке. - text: 'Ако не желите да Ваше дело буде неоткривено, молимо Вас да посетите Вашу страницу Approved Collection Items (Одобрени предмети из збирке) како бисте га уклонили из ове збирке: %{collection_items_url}' - faq_link_text: Често постављена питања о збиркама - more_info: - html: За више информација, посетите наша %{faq_link}. - text: 'За више информација, посетите наша Често постављена питања о збиркама: %{faq_url}' - subject: - anonymous: "[%{app_name}] Ваше дело је начињено анонимним" - anonymous_unrevealed: "[%{app_name}] Ваше дело је начињено анонимним и неоткривеним" - unrevealed: "[%{app_name}] Ваше дело је начињено неоткривеним" - unrevealed_info: Неоткривена дела нису укључена у излистане тагове и нису на страници Ваших радова. Свако ко буде пратио линк до Вашег дела ће добити обавештење да је дело тренутно неоткривено, и да није могуће да се приступи његовом садржају. - challenge_assignment_notification: - any: Било који - assignment: - html: Додељен Вам је следећи захтев у оквиру изазова %{link} на Archive of Our Own – AO3-у (Нашој сопственој архиви)! - description: 'Опис:' - due: 'Рок за oвај задатак је:' - html: - footer: Овај мејл сте добили јер сте се пријавили за изазов %{title}. За више информација о овом изазову и контакт информације модератора, молимо Вас, посетите %{footer_link}. - footer_link: профилну страницу изазова - look_up: Овај задатак можете потражити на %{link}. - look_up_link: Вашој страници Assignments (Задаци) - optional_tags: 'Произвољни тагови:' - prompt_url: 'Веб-адреса идеје:' - prompts: 'Идеје:' - recipient: 'Прималац:' - recipient_missing: 'Непознат: за помоћ, контактирајте модератора!' - subject: "[%{app_name}][%{collection_title}] Ваш задатак!" - text: - assignment: Додељен Вам је следећи захтев у оквиру изазова "%{collection_title}" (%{collection_url}) на Archive of Our Own – AO3-у (Нашој сопственој архиви)! - footer: Овај мејл сте добили јер сте се пријавили за %{title} изазов (%{url}). За више информација о овом изазову и контакт информације модератора, молимо Вас, посетите %{profile_url}. - look_up: Овај задатак можете потражити на Вашој страници Assignments (Задаци) на %{link}. - change_email: - changed: - html: "%{login}, имејл који је повезан са Вашим налогом је промењен у %{email}" - text: "%{login}, имејл који је повезан са Вашим налогом је промењен у %{email}" - subject: "[%{app_name}] Имејл је промењен" - collection_notification: - assignments_sent: - complete: Сви задаци су послати. - subject: Задаци послати - html: - received_message: 'Примили сте поруку о својој збирци %{collection_link}:' - text: - received_message: 'Примили сте поруку о својој збирци "%{collection_title}" (%{collection_url}):' - creatorship_notification: - explanation: Када сте коаутор неког дела, можете бити додани новим главама без обзира на Ваша подешавања о коауторству. Бићете додани и на сваки серијал у који је тај рад додан. - html: - creation: "%{creation_link} од %{pseud_links}" - edit_chapter: уредити поглавље - edit_series: уредити серијал - remove_chapter: Ако сте додани грешком или не желите да будете наведени као аутор, можете %{edit_chapter_link} да бисте уклонили себе са листе аутора. - remove_series: Ако сте додани грешком или не желите да будете наведени као аутор, можете %{edit_series_link} да бисте уклонили себе са листе аутора. - intro_chapter: 'Корисник %{adding_user} је навео Ваш псеудоним %{pseud} као коаутора следећег поглавља:' - intro_series: 'Корисник %{adding_user} је навео Ваш псеудоним %{pseud} као коаутора следећег серијала:' - subject: "[%{app_name}] Обавештење о коауторству" - text: - creation: "%{title} (%{url}) од %{pseuds}" - remove_chapter: 'Ако сте додани грешком или не желите да будете наведени као аутор, можете уредити поглавље дa бисте уклонили себе као аутора: %{url}' - remove_series: 'Ако сте додани грешком или не желите да будете наведени као аутор, можете уредити серијал дa бисте уклонили себе као аутора: %{url}' - creatorship_notification_archivist: - explanation: Пошто поступају по свом званичном капацитету као архивар Отворених Врата, дозвољено им је да Вас додају без позивнице, чак и ако Вам је опција коауторства искључена. - html: - creation: "%{creation_link} од %{pseud_links}" - edit_chapter: уредити поглавље - edit_series: уредити серијал - edit_work: уредити дело - remove_chapter: Ако сте додати грешком или не желите да будете наведени као креатор, можете %{edit_chapter_link} да би уклонили себе као креатора. - remove_series: Ако сте додати грешком или не желите да будете наведени као креатор, можете %{edit_series_link} да би уклонили себе као креатора. - remove_work: Ако сте додати грешком или не желите да будете наведени као креатор, можете %{edit_work_link} да би уклонили себе као креатора. - intro_chapter: 'Корисник %{archivist} је додао Ваш псеудоним %{pseud} као коаутора на следећем поглављу:' - intro_series: 'Корисник %{archivist} је додао Ваш псеудоним %{pseud} као коаутора на следећем серијалу:' - intro_work: 'Корисник %{archivist} је додао Ваш псеудоним %{pseud} као коаутора на следећем делу:' - subject: "[%{app_name}] Архиварска нотификација за коауторство" - text: - creation: "%{title} (%{url}) од %{pseuds}" - remove_chapter: 'Ако сте додати грешком или не желите да будете наведени као креатор, можете уредити поглавље да би уклонили себе као креатора: %{url}' - remove_series: 'Ако сте додати грешком или не желите да будете наведени као креатор, можете уредити серијал да би уклонили себе као креатора: %{url}' - remove_work: 'Ако сте додати грешком или не желите да будете наведени као креатор, можете уредити дело да би уклонили себе као креатора: %{url}' - creatorship_request: - html: - creation: "%{creation_link} од стране %{pseud_links}" - instructions: Можете да прихватите или одбијете овај захтев на Вашој страници %{page_name}. - page_name: Co-Creator Requests (Захтеви за коаутора) - intro_chapter: 'Корисник %{inviting_user} је позвао Ваш псеудоним %{pseud} да буде на листи коаутора следећег поглавља:' - intro_series: 'Корисник %{inviting_user} је позвао Ваш псеудоним %{pseud} да буде на листи коаутора следећег серијала:' - intro_work: 'Корисник %{inviting_user} је позвао Ваш псеудоним %{pseud} да буде на листи коаутора на следећем делу:' - subject: "[%{app_name}] Захтев за коаутора" - text: - creation: "%{title} (%{url}) од стране %{pseuds}" - instructions: 'Можете да прихватите или одбијете овај захтев на Вашој страници Co-Creator Requests (Захтеви за коаутора): %{url}' - delete_work_notification: - attachment: У прилогу је копија Вашег дела ако желите да га прегледате. - deleted_other: - html: Ваше дело %{title} је избрисано на захтев %{pseud}. - text: Ваше дело "%{title}" је избрисано на захтев %{pseud}. - deleted_yourself: - html: Ваше дело %{title} је избрисано на Ваш захтев. - text: Ваше дело %{title} је избрисано на Ваш захтев. - questions: - html: Ако имате питања, молимо Вас %{support}. - text: Ако имате питања, молимо Вас %{support} (%{url}). - subject: "[%{app_name}] Ваше дело је избрисано" - support: контактирајте Корисничку подршку - invitation_to_claim: - access: - text: У зависности од архиве, Ваша дела су можда унесена као ограничена само на регистроване кориснике (како се не би појављивала на Google претрагама). У овом случају, дела ће бити приступачна једино улогованим корисницима, осим ако не изаберете да их начините потпуно видљивима. За помоћ при откључавању, одрицању или брисању Ваших дела, молимо Вас да контактирате АО3 корисничку подршку. - claim_or_remove: - html: Прихватите или уклоните своја дела овде. - text: 'Прихватите или уклоните своја дела овде: %{claim_url}' - email_tips: Ако нас контактирате, молимо Вас да додате имејл адресе са @transformativeworks.org у своју контакт листу и да проверите свој спам фолдер за наш одговор. - html: - ao3_news: AO3 новостимa - contact_open_doors: контактираjте Отворена врата - contact_support: контактирате AO3 корисничку подршку - faq_page: страници са најчешћим питањима - open_doors: Отворена врата - tutorial_page: туторијал страници - introduction: - text: Добили сте овај имејл зато што је пројекат Отворена врата (%{open_doors_link}) у скорој прошлости унео једну архиву на %{app_name} (%{app_short_name} - %{app_url}), и верујемо да следећа фан-дела припадају Вама. Волели бисмо да Вам дамо шансу да их прихватите (или избришете/одрекнете их се) ако желите. У случају да већ немате налог под другим имејлом, желили бисмо да Вас позовемо да нам се придружите! - mistake: - text: Aко је ово грешка и ово нису Ваша дела, молимо Вас да их не бришете! Молимо Вас да контактирате Отворена врата (%{open_doors_link}) и ми ћемо то даље разрешити. - more_info: - text: Објаве о скорим селидбама архиве можете да прочитате на (%{news_link}), и да нађете додатне информације на страници са најчешћим питањима (%{open_doors_faq_link}) или туторијал страници (%{open_doors_tutorial_link}) Oтворених врата. За било која питања која нису одговорена у најчешће постављеним питањима, туторијалима или овом имејлу, молимо Вас да контактирате корисничку подршку на %{support_link}. - other_works: - text: Aко сте имали друга дела на увезеној архиви под имејл адресом којој више немате приступ, молимо Вас да контактирате Отворена Врата са било којим информацијама које би нам могле помоћи да утврдимо Ваш идентитет. - questions: - text: За даље упите, молимо Вас да контактирате АО3 корисничку подршку на %{support_link}. - redirects: Како би листе препорука и обележивачи били сачувани, веб адресе са унесене архиве могу бити на кратко време преусмерене на увезену копију ових дела (проверите чланак објаве Ваше архиве како бисте били сигурни). Ако сте већ поставили копију ових дела и НИСТЕ користили поставу са URL функције, постојаће две копије истог дела на архиви. - subject: "[%{app_name}] Позив за прихватање дела" - unwanted: - text: Aко ова дела припадају Вама, али их не желите, можете се одрећи ауторских права (како би дела остала на AO3-ју али са Вашим именом уклоњеним) или их избрисати (како би дела била у потпуности уклоњена са AO3-ja). Нема потребе да додајете ова дела ни на који налог како бисте их се одрекли или их обрисали--можете то урадити са линка за прихватање дела у тексту изнад. (За помоћ, молимо Вас да контактирате корисничку подршку на %{support_link}.) - update_redirect: - text: Aко бисте желели да Отворена врата ажурирају линк за преусмераванје тако да приказује Ваша ранија дела, молимо Вас да обришете увезену копију и да контактирате Отворена врата на %{open_doors_link} са својим AO3 корисничким именом, својим корисничким именом на увезеној архиви, и са именом и URL-ом фан-дела на које бисте желели да преусмеравање указује. (Aко имате више дела за која бисте желели да промените преусмеравање, можете их побројати у једном имејлу.) - uploaded_list: 'Постављена дела укључују:' - invite_increase_notification: - body: - few: Само смо хтели да Вас обавестимо да имате %{count} нове позивнице, које се могу користити за креирање нових налога на АО3-у. Можете позвати пријатеља на %{invitation_page}. - one: Само смо хтели да Вас обавестимо да имате %{count} нову позивницу, која се може користити за креирање нових налога на АО3-у. Можете позвати пријатеља на %{invitation_page}. - other: Само смо хтели да Вас обавестимо да имате %{count} нових позивница, које се могу користити за креирање нових налога на АО3-у. Можете позвати пријатеља на %{invitation_page}. - invitation_page_link_text: Вашој страници позивница - subject: "[%{app_name}] Нове Позивнице" - invite_request_declined: - main: - few: Са жаљењем Вас обавештавамо да се Ваш захтев за %{count} нове позивнице не може тренутно испунити. - one: Са жаљењем Вас обавештавамо да се Ваш захтев за %{count} нову позивницу не може тренутно испунити. - other: Са жаљењем Вас обавештавамо да се Ваш захтев за %{count} нових позивница не може тренутно испунити. - reason: 'Ваш захтев је био:' - subject: "[%{app_name}] Захтев за додатни позивни код је одбијен" - recipient_notification: - html: - collection: Поклон дело за Вас је објављено у %{collection_link} збирци на АО3-у! - no_collection: Поклон дело за Вас је објављено на АО3-у! - subject: - collection: "[%{app_name}][%{collection_title}] Поклон дело за Вас из %{collection_title}" - no_collection: "[%{app_name}] Поклон дело за Вас" - text: - collection: Поклон дело за Вас је објављено у "%{collection_title}" збирци (%{collection_url}) на АО3-у! - signup_notification: - activate: - html: "%{activate_account_link}." - text: 'Активирај свој налог: %{activate_account_url}' - activate_your_account: Активирај свој налог - admin_posts: админ постови Архиве - bye: Надамо се да ћеш уживати користећи Архиву. - contact_support: контактирај наш тим корисничке подршке - faq: Најчешћа питања - features: - html: Када твој налог постане активан, можеш да објављујеш своје фановске радове, поставиш email обавештења која ће ти јавити ажурирања од твојих омиљених ствараоца или радова, подесиш жељени изглед сајта и како ради, пратиш радове које си прегледао/прегледала на Архиви путем историје, и још много више. - text: Када твој налог постане активан, можеш да објављујеш своје фановске радове, поставиш email обавештења која ће ти јавити ажурирања од твојих омиљених ствараоца или радова, подесиш жељени изглед сајта и како ради, пратиш радове које си прегледао/прегледала на Архиви путем историје, и још много више. - information: - html: Много информација и савета о томе како се користи Архива се налазе у делу %{faq_link}. Последње вести о развоју сајта ћеш наћи у делу %{admin_posts_link}. Уколико ти треба даља помоћ, наиђеш на баг, или имаш питања или коментара, %{contact_support_link}, који су увек ту да помогну. - text: 'Много информација и савета о томе како се користи Архива се налазе у најчешћим питањима %{faq_url}. Последње вести о развоју сајта ћеш наћи у админ постовима Архиве %{admin_posts_url}. Уколико ти треба даља помоћ, наиђеш на баг, или имаш питања или коментара, контактирај наш тим корисничке подршке, који су увек ту да помогну: %{contact_support_url}.' - welcome: Добродошли у Нашу сопствену архиву, %{login}! - users: - mailer: - reset_password_instructions: - expiration: Ако не искористите овај линк да ресетујете своју лозинку у року од недељу дана, истећи ће и мораћете затражити нови. - intro: 'неко је затражио ресетовање лозинке за Bаш налог. Можете променити лозинку Bашег налога тако што ћете пратити линк испод и унети Вашу нову лозинку:' - link_title: Промени моју лозинку. - subject: "[%{app_name}] Ресетујте своју лозинку" - unrequested: Уколико нисте затражили ово ресетовање лозинке, можете занемарити овај имејл и Ваша претходна лозинка ће наставити да ради. From 812d9663152c4860760dae299fc23b8db60f4f8b Mon Sep 17 00:00:00 2001 From: Claire Carden <96350691+smclairecarden@users.noreply.github.com> Date: Fri, 17 May 2024 11:19:32 -0500 Subject: [PATCH 003/181] AO3-6570 Import Work error message overflows for long URLs (#4716) * #AO3-6570 - set word-wrap on error element. * #AO3-6570 - change element. * #6570 - indent properly --- public/stylesheets/site/2.0/22-system-messages.css | 1 + 1 file changed, 1 insertion(+) diff --git a/public/stylesheets/site/2.0/22-system-messages.css b/public/stylesheets/site/2.0/22-system-messages.css index c174fcd88ca..6fc83bd5d3d 100644 --- a/public/stylesheets/site/2.0/22-system-messages.css +++ b/public/stylesheets/site/2.0/22-system-messages.css @@ -37,6 +37,7 @@ div.error { clear: right; box-shadow: inset 1px 1px 2px; border-radius: 0.25em; + word-wrap: break-word; } .caution { From 3e683fceac93ed56e090cea7baee1d135f1b878b Mon Sep 17 00:00:00 2001 From: potpotkettle <40988246+potpotkettle@users.noreply.github.com> Date: Sat, 18 May 2024 01:20:39 +0900 Subject: [PATCH 004/181] AO3-6087 Stop removing non-printable Unicode characters (#4798) * AO3-6087 Stop removing non-printable Unicode characters especially, zero-width joiner, zero-width non-joiner and word joiner * AO3-6087 (tweaking test messages) --- lib/html_cleaner.rb | 5 ----- spec/lib/html_cleaner_spec.rb | 19 +++++++++++++++---- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/lib/html_cleaner.rb b/lib/html_cleaner.rb index e594cd595eb..7201d22ed5a 100644 --- a/lib/html_cleaner.rb +++ b/lib/html_cleaner.rb @@ -36,11 +36,6 @@ def fix_bad_characters(text) # argh, get rid of ____spacer____ inserts text.gsub! "____spacer____", "" - # trash a whole bunch of crappy non-printing format characters stuck - # in most commonly by MS Word - # \p{Cf} matches all unicode char in the "other, format" category - text.gsub!(/\p{Cf}/u, '') - return text end diff --git a/spec/lib/html_cleaner_spec.rb b/spec/lib/html_cleaner_spec.rb index 4dabcf4e8c3..038a14db1ac 100644 --- a/spec/lib/html_cleaner_spec.rb +++ b/spec/lib/html_cleaner_spec.rb @@ -524,6 +524,21 @@ expect(fix_bad_characters("„‚nörmäl’—téxt‘“")).to eq("„‚nörmäl’—téxt‘“") end + it "does not touch zero-width non-joiner" do + string = ["A".ord, 0x200C, "A".ord] # "A[zwnj]A" + expect(fix_bad_characters(string.pack("U*")).unpack("U*")).to eq(string) + end + + it "does not touch zero-width joiner" do + string = ["A".ord, 0x200D, "A".ord] # "A[zwj]A" + expect(fix_bad_characters(string.pack("U*")).unpack("U*")).to eq(string) + end + + it "does not touch word joiner" do + string = ["A".ord, 0x2060, "A".ord] # "A[wj]A" + expect(fix_bad_characters(string.pack("U*")).unpack("U*")).to eq(string) + end + it "should remove invalid unicode chars" do bad_string = [65, 150, 65].pack("C*") # => "A\226A" expect(fix_bad_characters(bad_string)).to eq("AA") @@ -540,10 +555,6 @@ it "should remove the spacer" do expect(fix_bad_characters("A____spacer____A")).to eq("AA") end - - it "should remove unicode chars in the 'other, format' category" do - expect(fix_bad_characters("A\xE2\x81\xA0A")).to eq("AA") - end end describe "add_paragraphs_to_text" do From 28897876ce087eaa07cf353c691930185ecc3529 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20B?= Date: Fri, 17 May 2024 18:22:56 +0200 Subject: [PATCH 005/181] AO3-5860 Prevent leaving comments or kudos when logged in as admin (#4378) * Ask admins to log out before commenting https://otwarchive.atlassian.net/browse/AO3-5860 * Ask admins to log out before leaving kudos https://otwarchive.atlassian.net/browse/AO3-5860 * Clean up now irrelevant code * Update reminder text https://otwarchive.atlassian.net/browse/AO3-5860?focusedCommentId=356267 * Controller tests instead of integration tests * Hide kudos/comments button as admin https://otwarchive.atlassian.net/browse/AO3-5860?focusedCommentId=356272 * Restrict commenting on works only? * Attempt at disabling all admin comments * Revert "Attempt at disabling all admin comments" This reverts commit 33b89e09fd3ed6901802d15f79283af7d8aeb223. * Fix test to not restrict admin comments on tags * ? spacing IDE? * Hound * Hide whole form, not solely button * Prevent admin from commenting on anything * Update old test for new behavior * Check message and redirection at once * Add test for no reply comment * Remove empty if Not exactly sure why I wrote it in the first place honestly... * Now useless admin only form * Lost EOF newline * Actually hide Reply link * Check not admin on all "add comment" routes * Consistent location for check * Updated test for new behavior * Unneeded change * More precise message * Better test description * Missing test * Normalize i18n files Unrelated to the rest of the PR Just caught by a different hound * Forgot to update similar tests indeed * Handle :js format * Revert "Handle :js format" This reverts commit 5ddfb2e209bab38abe496ca673976ce91ff46b0c. * Make sure admin cannot create kudos through AJAX * Oops * Doing two things as once... To make sure each takes forever due to silly mistakes * Actual non silly merge conflict * Intended behavior? * Update ts( to t( In for a penny * If you start doing something... You might as well do it right * Test update * Acceptable HTML --- app/controllers/comments_controller.rb | 1 + app/controllers/kudos_controller.rb | 1 + app/helpers/comments_helper.rb | 1 + app/views/comments/_comment_form.html.erb | 83 ++++++++----------- app/views/comments/_commentable.html.erb | 6 +- config/locales/views/en.yml | 24 +++++- features/admins/admin_settings.feature | 5 -- .../comments_and_kudos/add_comment.feature | 27 ++++++ features/comments_and_kudos/kudos.feature | 6 ++ features/importing/work_import.feature | 2 +- .../tags_and_wrangling/tag_comment.feature | 15 +--- spec/controllers/comments_controller_spec.rb | 36 +++++--- spec/controllers/kudos_controller_spec.rb | 20 +++++ 13 files changed, 150 insertions(+), 77 deletions(-) diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index f4e96e3ca2c..b7c8a3df1b7 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -30,6 +30,7 @@ class CommentsController < ApplicationController before_action :check_permission_to_moderate, only: [:approve, :reject] before_action :check_permission_to_modify_frozen_status, only: [:freeze, :unfreeze] before_action :check_permission_to_modify_hidden_status, only: [:hide, :unhide] + before_action :admin_logout_required, only: [:new, :create, :add_comment_reply] include BlockHelper diff --git a/app/controllers/kudos_controller.rb b/app/controllers/kudos_controller.rb index d575bdc65fe..bedb0a8342a 100644 --- a/app/controllers/kudos_controller.rb +++ b/app/controllers/kudos_controller.rb @@ -1,5 +1,6 @@ class KudosController < ApplicationController skip_before_action :store_location + before_action :admin_logout_required, only: [:create] def index @work = Work.find(params[:work_id]) diff --git a/app/helpers/comments_helper.rb b/app/helpers/comments_helper.rb index 95f5ce1b72c..bdb68107905 100644 --- a/app/helpers/comments_helper.rb +++ b/app/helpers/comments_helper.rb @@ -112,6 +112,7 @@ def can_reply_to_comment?(comment) return false if comment_parent_hidden?(comment) return false if blocked_by_comment?(comment) return false if blocked_by?(comment.ultimate_parent) + return false if logged_in_as_admin? return true unless guest? diff --git a/app/views/comments/_comment_form.html.erb b/app/views/comments/_comment_form.html.erb index e6af2d9d9d8..eb4212f98f5 100644 --- a/app/views/comments/_comment_form.html.erb +++ b/app/views/comments/_comment_form.html.erb @@ -5,9 +5,9 @@
<%= form_for value_for_comment_form(commentable, comment), remote: !comment.new_record?, authenticity_token: true, html: { id: "comment_for_#{commentable.id}" } do |f| %>
- <%= ts("Post Comment") %> + <%= t(".legend") %> - <% # here come the hacks (hidden fields to transmit various info to the create action) %> + <%# here come the hacks (hidden fields to transmit various info to the create action) %> <% if commentable.is_a?(Tag) %> <%= hidden_field_tag :tag_id, commentable.name %> <% end %> @@ -28,62 +28,51 @@ <% if comments_are_moderated(commentable) && !current_user_is_work_creator(commentable) %>

- <%= ts("This work's creator has chosen to moderate comments on the work. Your comment will not appear until it has been approved by the creator.") %> + <%= t(".moderation_forewarning") %>

<% end %> <% if logged_in? %> <% if current_user_is_anonymous_creator(commentable) %>

- <%= ts("While this work is anonymous, comments you post will also be listed anonymously.") %> + <%= t(".anonymous_forewarning") %>

<% end %> <% if current_user.pseuds.count > 1 %> -

<%= ts("Comment as") %> <%= f.collection_select :pseud_id, current_user.pseuds, :id, :name, {:selected => (comment.pseud ? comment.pseud.id.to_s : current_user.default_pseud.id.to_s)}, :id => "comment_pseud_id_for_#{commentable.id}", :title => ts("Choose Name") %> +

<%= t(".comment_as") %> <%= f.collection_select :pseud_id, current_user.pseuds, :id, :name, { selected: (comment.pseud ? comment.pseud.id.to_s : current_user.default_pseud.id.to_s) }, id: "comment_pseud_id_for_#{commentable.id}", title: t(".choose_name_field_title") %> <% if controller.controller_name == "inbox" %> - <% if commentable.by_anonymous_creator? %> - <%= ts("to") %> <%= "Anonymous Creator" %> <%= ts("on") %> <%= commentable_description_link(commentable) %> - <% else %> - <%= ts("to") %> <%= get_commenter_pseud_or_name(commentable) %> <%= ts("on") %> <%= commentable_description_link(commentable) %> - <% end %> + <%= t(".inbox_reference_html", + commentable_creator: commentable.by_anonymous_creator? ? t(".anonymous_creator") : get_commenter_pseud_or_name(commentable), + commentable_link: commentable_description_link(commentable)) %> <% end %> (<%= allowed_html_instructions %>)

<% else %> -

<%= ts("Comment as") %> - <%= f.hidden_field :pseud_id, :value => "#{current_user.default_pseud.id}", :id => "comment_pseud_id_for_#{commentable.id}" %> +

<%= t(".comment_as") %> + <%= f.hidden_field :pseud_id, value: current_user.default_pseud.id.to_s, id: "comment_pseud_id_for_#{commentable.id}" %> <% if controller.controller_name == "inbox" %> - <% if commentable.by_anonymous_creator? %> - <%= ts("to") %> <%= "Anonymous Creator" %> <%= ts("on") %> <%= commentable_description_link(commentable) %> - <% else %> - <%= ts("to") %> <%= get_commenter_pseud_or_name(commentable) %> <%= ts("on") %> <%= commentable_description_link(commentable) %> - <% end %> + <%= t(".inbox_reference_html", + commentable_creator: commentable.by_anonymous_creator? ? t(".anonymous_creator") : get_commenter_pseud_or_name(commentable), + commentable_link: commentable_description_link(commentable)) %> <% end %>

(<%= allowed_html_instructions %>)

<% end %> - <% elsif logged_in_as_admin? %> -

<%= ts("Comment as") %> - <%= f.hidden_field :name, :value => "#{current_admin.login}", :id => "comment_name_for_#{commentable.id}" %> - <%= f.hidden_field :email, :value => "#{current_admin.email}", :id => "comment_email_for_#{commentable.id}" %> -

-

(<%= allowed_html_instructions %>)

- <% else %>
-
<%= ts("Note") %>:
-
<%=ts("All fields are required. Your email address will not be published.") %>
-
<%= f.label "name_for_#{commentable.id}", ts("Guest name: ") %>
+
<%= t(".landmark.note") %>:
+
<%= t(".guest_instructions") %>
+
<%= f.label "name_for_#{commentable.id}", t(".guest_name") %>
- <%= f.text_field :name, :id => "comment_name_for_#{commentable.id}" %> - <%= live_validation_for_field("comment_name_for_#{commentable.id}", :failureMessage => ts('Please enter your name.')) %> + <%= f.text_field :name, id: "comment_name_for_#{commentable.id}" %> + <%= live_validation_for_field("comment_name_for_#{commentable.id}", failureMessage: t(".guest_name_failure")) %>
-
<%= f.label "email_for_#{commentable.id}", ts("Guest email: ") %>
+
<%= f.label "email_for_#{commentable.id}", t(".guest_email") %>
- <%= f.text_field :email, :id => "comment_email_for_#{commentable.id}" %> - <%= live_validation_for_field("comment_email_for_#{commentable.id}", :failureMessage => ts('Please enter your email address.')) %> + <%= f.text_field :email, id: "comment_email_for_#{commentable.id}" %> + <%= live_validation_for_field("comment_email_for_#{commentable.id}", failureMessage: t(".guest_email_failure")) %>

(<%= allowed_html_instructions %>)

@@ -91,25 +80,25 @@

<% content_id = "comment_content_for_#{commentable.id}" %> - - <%= f.text_area :comment_content, :id => content_id, :class => "comment_form observe_textlength", :title => ts("Enter Comment") %> + + <%= f.text_area :comment_content, id: content_id, class: "comment_form observe_textlength", title: t(".comment_field_title") %>

<%= generate_countdown_html("comment_content_for_#{commentable.id}", ArchiveConfig.COMMENT_MAX) %> - <%= live_validation_for_field("comment_content_for_#{commentable.id}", - :failureMessage => ts('Brevity is the soul of wit, but we need your comment to have text in it.'), - :maximum_length => ArchiveConfig.COMMENT_MAX, - :tooLongMessage => ts("must be less than %{count} characters long.", :count => ArchiveConfig.COMMENT_MAX)) %> + <%= live_validation_for_field "comment_content_for_#{commentable.id}", + failureMessage: t(".comment_too_short"), + maximum_length: ArchiveConfig.COMMENT_MAX, + tooLongMessage: t(".comment_too_long", count: ArchiveConfig.COMMENT_MAX) %>

- <%= f.submit button_name, :id => "comment_submit_for_#{commentable.id}", data: {disable_with: ts("Please wait...")} %> - <% if controller.controller_name == 'inbox' %> - <%= ts("Cancel") %> - <% elsif comment.persisted? %> - <%= cancel_edit_comment_link(comment) %> - <% elsif commentable.is_a?(Comment) || commentable.is_a?(CommentDecorator) %> - <%= cancel_comment_reply_link(commentable) %> - <% end %> -

+ <%= f.submit button_name, id: "comment_submit_for_#{commentable.id}", data: { disable_with: t(".processing_message") } %> + <% if controller.controller_name == 'inbox' %> + <%= t(".cancel_action") %> + <% elsif comment.persisted? %> + <%= cancel_edit_comment_link(comment) %> + <% elsif commentable.is_a?(Comment) || commentable.is_a?(CommentDecorator) %> + <%= cancel_comment_reply_link(commentable) %> + <% end %> +

<% end %>
diff --git a/app/views/comments/_commentable.html.erb b/app/views/comments/_commentable.html.erb index 971098077bc..7fbd1bbdbb7 100644 --- a/app/views/comments/_commentable.html.erb +++ b/app/views/comments/_commentable.html.erb @@ -28,7 +28,7 @@
  • <%= link_to ts('Back to AO3 News Index'), admin_posts_path %>
  • <% end %> - <% if @work && !is_author_of?(@work) && !blocked_by?(@work) %> + <% if @work && !is_author_of?(@work) && !blocked_by?(@work) && !logged_in_as_admin? %>
  • <%= form_for(:kudo, url: kudos_path, html: { method: 'post', id: 'new_kudo' }) do |kudo_form| %> <%= kudo_form.hidden_field :commentable_id, :value => @work.id %> @@ -113,6 +113,10 @@

    <%= t(".blocked") %>

    + <% elsif logged_in_as_admin? %> +

    + <%= t(".logged_as_admin") %> +

    <% else %>
    diff --git a/config/locales/views/en.yml b/config/locales/views/en.yml index 6348e33767e..13fe79a1895 100644 --- a/config/locales/views/en.yml +++ b/config/locales/views/en.yml @@ -319,7 +319,7 @@ en: admins: index: confidentiality_reminder: You are now logged in as an admin. That means you will probably encounter information that is personal or confidential (e.g. usernames, email and IP addresses, creator names on anonymous works, etc). Please do not use this information in ways unrelated to your OTW role. If you have questions about what you can or cannot do with information you see here, contact your committee chair(s). - log_out_reminder: Please remember to log out before resuming your normal site activity and leaving comments and kudos! + log_out_reminder: Please remember to log out before resuming your normal site activity! page_title: Hi, %{login}! responsibility: With great power comes great responsibility. roles: @@ -414,6 +414,27 @@ en: unreviewed_by_collection: Works and bookmarks listed here have been added to a collection but need approval from a collection moderator before they are listed in the collection. page_heading: Items by %{username} in Collections comments: + comment_form: + anonymous_creator: Anonymous Creator + anonymous_forewarning: While this work is anonymous, comments you post will also be listed anonymously. + cancel_action: Cancel + choose_name_field_title: Choose Name + comment_as: Comment as + comment_field_title: Enter Comment + comment_too_long: must be less than %{count} characters long. + comment_too_short: Brevity is the soul of wit, but we need your comment to have text in it. + guest_email: Guest email + guest_email_failure: Please enter your email address. + guest_instructions: All fields are required. Your email address will not be published. + guest_name: Guest name + guest_name_failure: Please enter your name. + inbox_reference_html: to %{commentable_creator} on %{commentable_link} + landmark: + comment: Comment + note: Note + legend: Post Comment + moderation_forewarning: This work's creator has chosen to moderate comments on the work. Your comment will not appear until it has been approved by the creator. + processing_message: Please wait... commentable: actions: comment: Comment @@ -421,6 +442,7 @@ en: blocked: Sorry, you have been blocked by one or more of this work's creators. guest_comments_disabled: Sorry, the Archive doesn't allow guests to comment right now. invite_to_collections_link: Invite To Collections + logged_as_admin: Please log out of your admin account to comment. permissions: admin_post: alt_action: You can however %{support_link} with any feedback or questions. diff --git a/features/admins/admin_settings.feature b/features/admins/admin_settings.feature index 693daf83afe..c6210e35709 100644 --- a/features/admins/admin_settings.feature +++ b/features/admins/admin_settings.feature @@ -140,11 +140,6 @@ Feature: Admin Settings Page Scenario: Tag comments are not affected when guest comments are turned off Given guest comments are off And a fandom exists with name: "Stargate SG-1", canonical: true - When I am logged in as a super admin - And I view the tag "Stargate SG-1" with comments - Then I should not see "Sorry, the Archive doesn't allow guests to comment right now." - When I post the comment "Important policy decision" on the tag "Stargate SG-1" - Then I should see "Comment created!" When I am logged in as a tag wrangler And I view the tag "Stargate SG-1" with comments Then I should not see "Sorry, the Archive doesn't allow guests to comment right now." diff --git a/features/comments_and_kudos/add_comment.feature b/features/comments_and_kudos/add_comment.feature index 0b33e5d8bc7..22eb9f46dc3 100644 --- a/features/comments_and_kudos/add_comment.feature +++ b/features/comments_and_kudos/add_comment.feature @@ -224,3 +224,30 @@ Scenario: Users with different time zone preferences should see the time in thei And I view the work "Generic Work" with comments Then I should see "AEST" within ".posted.datetime" And I should see "AEST" within ".edited.datetime" + +Scenario: Cannot comment (no form) while logged as admin + + Given the work "Generic Work" + And I am logged in as an admin + And I view the work "Generic Work" + Then I should see "Generic Work" + And I should not see "Post Comment" + And I should not see a "Comment" button + And I should see "Please log out of your admin account to comment." + +Scenario: Cannot reply to comments (no button) while logged as admin + + Given the work "Generic Work" + When I am logged in as "commenter" + And I view the work "Generic Work" + And I post a comment "Woohoo" + When I am logged in as an admin + And I view the work "Generic Work" + And I follow "Comments (1)" + Then I should see "Woohoo" + And I should not see "Reply" + When I am logged out + And I view the work "Generic Work" + And I follow "Comments (1)" + Then I should see "Woohoo" + And I should see "Reply" diff --git a/features/comments_and_kudos/kudos.feature b/features/comments_and_kudos/kudos.feature index f5d5fd03cc1..c6f85220ce1 100644 --- a/features/comments_and_kudos/kudos.feature +++ b/features/comments_and_kudos/kudos.feature @@ -195,3 +195,9 @@ Feature: Kudos And the kudos cache has expired And I view the work "Interesting beans" Then I should not see "newusername1 left kudos on this work!" + + Scenario: Cannot leave kudos (no button) while logged as admin + Given I am logged in as an admin + And I view the work "Awesome Story" + Then I should see "Awesome Story" + And I should not see a "Kudos ♥" button diff --git a/features/importing/work_import.feature b/features/importing/work_import.feature index a4325c6d809..be878c3d78b 100644 --- a/features/importing/work_import.feature +++ b/features/importing/work_import.feature @@ -163,7 +163,7 @@ Feature: Import Works When I am logged out And I go to the "Detected Title" work page And I follow "Yes, Continue" - Then I should see "Guest name:" + Then I should see "Guest name" Scenario: Imported works can have comments disabled to guests When I start importing "http://import-site-with-tags" with a mock website diff --git a/features/tags_and_wrangling/tag_comment.feature b/features/tags_and_wrangling/tag_comment.feature index 63eb3c8ce66..c52a2777335 100644 --- a/features/tags_and_wrangling/tag_comment.feature +++ b/features/tags_and_wrangling/tag_comment.feature @@ -43,24 +43,17 @@ I'd like to comment on a tag' Scenario: Multiple comments on a tag increment correctly Given the following activated tag wranglers exist - | login | - | dizmo | + | login | + | dizmo | + | someone_else | And a fandom exists with name: "Stargate Atlantis", canonical: true When I am logged in as "dizmo" When I post the comment "Yep, we should have a Stargate franchise metatag." on the tag "Stargate Atlantis" - When I am logged in as an admin + When I am logged in as "someone_else" When I post the comment "Important policy decision" on the tag "Stargate Atlantis" When I view the tag "Stargate Atlantis" Then I should see "2 comments" - Scenario: admin can also comment on tags, issue 1428 - - Given a fandom exists with name: "Stargate Atlantis", canonical: true - When I am logged in as an admin - When I post the comment "Important policy decision" on the tag "Stargate Atlantis" via web - When I view the tag "Stargate Atlantis" - Then I should see "1 comment" - Scenario: Issue 2185: email notifications for tag commenting; TO DO: replies to comments Given the following activated tag wranglers exist diff --git a/spec/controllers/comments_controller_spec.rb b/spec/controllers/comments_controller_spec.rb index 55750e4ca57..784dc97d8b5 100644 --- a/spec/controllers/comments_controller_spec.rb +++ b/spec/controllers/comments_controller_spec.rb @@ -174,6 +174,15 @@ it_redirects_to_with_error(work_path(work), "Sorry, this work doesn't allow comments.") end end + + context "when logged in as an admin" do + before { fake_login_admin(create(:admin)) } + + it "redirects to root with notice prompting log out" do + get :add_comment_reply, params: { comment_id: comment.id } + it_redirects_to_with_notice(root_path, "Please log out of your admin account first!") + end + end end shared_examples "guest cannot reply to a user with guest replies disabled" do @@ -308,10 +317,9 @@ context "when logged in as an admin" do before { fake_login_admin(create(:admin)) } - it "renders the :new template" do + it "redirects to root with notice prompting log out" do get :new, params: { tag_id: fandom.name } - expect(response).to render_template("new") - expect(assigns(:name)).to eq("Fandom") + it_redirects_to_with_notice(root_path, "Please log out of your admin account first!") end end @@ -543,16 +551,11 @@ context "when logged in as an admin" do before { fake_login_admin(create(:admin)) } - it "posts the comment and shows it in context" do + it "redirects to root with notice prompting log out" do post :create, params: { tag_id: fandom.name, comment: anon_comment_attributes } + it_redirects_to_with_notice(root_path, "Please log out of your admin account first!") comment = Comment.last - expect(comment.commentable).to eq fandom - expect(comment.name).to eq anon_comment_attributes[:name] - expect(comment.email).to eq anon_comment_attributes[:email] - expect(comment.comment_content).to include anon_comment_attributes[:comment_content] - path = comments_path(tag_id: fandom.to_param, - anchor: "comment_#{comment.id}") - expect(response).to redirect_to path + expect(comment).to eq nil end end @@ -636,6 +639,17 @@ expect(cookies[:flash_is_set]).to eq(1) end end + + context "when logged in as an admin" do + let(:work) { create(:work) } + + before { fake_login_admin(create(:admin)) } + + it "redirects to root with notice prompting log out" do + post :create, params: { work_id: work.id, comment: anon_comment_attributes } + it_redirects_to_with_notice(root_path, "Please log out of your admin account first!") + end + end end context "when the commentable is an admin post" do diff --git a/spec/controllers/kudos_controller_spec.rb b/spec/controllers/kudos_controller_spec.rb index 0a1e12f6c79..a2c549c736b 100644 --- a/spec/controllers/kudos_controller_spec.rb +++ b/spec/controllers/kudos_controller_spec.rb @@ -208,5 +208,25 @@ end end end + + context "when kudos giver is admin" do + let(:work) { create(:work) } + let(:admin) { create(:admin) } + + before { fake_login_admin(admin) } + + it "redirects to root with notice prompting log out" do + post :create, params: { kudo: { commentable_id: work.id, commentable_type: "Work" } } + it_redirects_to_with_notice(root_path, "Please log out of your admin account first!") + expect(assigns(:kudo)).to be_nil + end + + context "with format: :js" do + it "does not create any kudo" do + post :create, params: { kudo: { commentable_id: work.id, commentable_type: "Work" }, format: :js } + expect(assigns(:kudo)).to be_nil + end + end + end end end From d6d6e207ef87ed36ff98cfdc3c5b10cd7fad80e4 Mon Sep 17 00:00:00 2001 From: de3sw2aq1 Date: Fri, 17 May 2024 11:23:28 -0500 Subject: [PATCH 006/181] AO3-5506 Don't include hidden works in Readings (#4781) * AO3-5506 Don't include hidden works in Readings * Require works to be visible to mark for later * Hide draft works and hidden by admin works from the reading history * Test for visibility of hidden by admin works in reading history * simplify reading.feature test --- app/controllers/readings_controller.rb | 2 +- app/controllers/works_controller.rb | 2 +- features/other_a/reading.feature | 18 ++++++++++++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/app/controllers/readings_controller.rb b/app/controllers/readings_controller.rb index 6ae84d496a0..ba1314e4d6f 100644 --- a/app/controllers/readings_controller.rb +++ b/app/controllers/readings_controller.rb @@ -16,7 +16,7 @@ def index @readings = @readings.where(toread: true) @page_subtitle = ts("Marked For Later") end - @readings = @readings.order("last_viewed DESC").page(params[:page]) + @readings = @readings.left_joins(:work).merge(Work.visible_to_registered_user).or(Work.where(id: nil)).order("last_viewed DESC").page(params[:page]) end def destroy diff --git a/app/controllers/works_controller.rb b/app/controllers/works_controller.rb index 1f7f70832cf..1d9f52e7635 100755 --- a/app/controllers/works_controller.rb +++ b/app/controllers/works_controller.rb @@ -13,7 +13,7 @@ class WorksController < ApplicationController # admins should have the ability to edit tags (:edit_tags, :update_tags) as per our ToS before_action :check_ownership_or_admin, only: [:edit_tags, :update_tags] before_action :log_admin_activity, only: [:update_tags] - before_action :check_visibility, only: [:show, :navigate, :share] + before_action :check_visibility, only: [:show, :navigate, :share, :mark_for_later, :mark_as_read] before_action :load_first_chapter, only: [:show, :edit, :update, :preview] diff --git a/features/other_a/reading.feature b/features/other_a/reading.feature index 3ce2eabfe9a..5ec4f4d4e79 100644 --- a/features/other_a/reading.feature +++ b/features/other_a/reading.feature @@ -245,3 +245,21 @@ Feature: Reading count And I go to the homepage Then I should see "Some Work V2" And I should not see "Some Work V1" + + Scenario: A user cannot see hidden by admin works in their reading history + + Given I am logged in as "writer" + When I post the work "Testy" + Then I should see "Work was successfully posted" + When I am logged in as "reader" + And I view the work "Testy" + Then I should see "Mark for Later" + When I follow "Mark for Later" + Then I should see "This work was added to your Marked for Later list." + When I am logged in as a "policy_and_abuse" admin + And I view the work "Testy" + And I follow "Hide Work" + Then I should see "Item has been hidden." + When I am logged in as "reader" + And I go to reader's reading page + Then I should not see "Testy" From 44ed7b9377faace49de0cabc04708514be7659d1 Mon Sep 17 00:00:00 2001 From: Bilka Date: Fri, 17 May 2024 18:24:27 +0200 Subject: [PATCH 007/181] AO3-6704 Use URL from current host in abuse report response mailer preview (#4782) * AO3-6704 Use URL from current host in abuse report mailer preview * AO3-6704 Match mailer preview name to mailer name --- test/mailers/previews/user_mailer_preview.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/mailers/previews/user_mailer_preview.rb b/test/mailers/previews/user_mailer_preview.rb index 5fa4f430314..006547dea1e 100644 --- a/test/mailers/previews/user_mailer_preview.rb +++ b/test/mailers/previews/user_mailer_preview.rb @@ -1,7 +1,7 @@ class UserMailerPreview < ApplicationMailerPreview # Sent to a user when they submit an abuse report - def abuse_report_response - abuse_report = create(:abuse_report) + def abuse_report + abuse_report = create(:abuse_report, url: "https://#{ArchiveConfig.APP_HOST}/tags/1984%20-%20George%20Orwell") UserMailer.abuse_report(abuse_report.id) end From 9719025c53c7a4417b8dc89a8c3a20d4f1202401 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 May 2024 09:26:58 -0700 Subject: [PATCH 008/181] AO3-6727 Bump nokogiri from 1.16.3 to 1.16.5 (#4809) Bump nokogiri from 1.16.3 to 1.16.5 Bumps [nokogiri](https://github.com/sparklemotion/nokogiri) from 1.16.3 to 1.16.5. - [Release notes](https://github.com/sparklemotion/nokogiri/releases) - [Changelog](https://github.com/sparklemotion/nokogiri/blob/main/CHANGELOG.md) - [Commits](https://github.com/sparklemotion/nokogiri/compare/v1.16.3...v1.16.5) --- updated-dependencies: - dependency-name: nokogiri dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 9bd481f3879..5fc24af30f3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -365,7 +365,7 @@ GEM mime-types-data (~> 3.2015) mime-types-data (3.2024.0206) mini_mime (1.1.2) - mini_portile2 (2.8.5) + mini_portile2 (2.8.6) minitest (5.22.2) mono_logger (1.1.2) multi_json (1.15.0) @@ -399,7 +399,7 @@ GEM newrelic_rpm (9.7.1) nio4r (2.7.0) nkf (0.2.0) - nokogiri (1.16.3) + nokogiri (1.16.5) mini_portile2 (~> 2.8.2) racc (~> 1.4) orm_adapter (0.5.0) From 3ae1433a30e2940c4e467a1d18d084384748bb05 Mon Sep 17 00:00:00 2001 From: Bilka Date: Fri, 17 May 2024 18:28:13 +0200 Subject: [PATCH 009/181] AO3-6017 Add role of commenter to comment notification emails (#4683) * AO3-6017 Add username and role of commenter to comment notification emails * AO3-6017 Use same locale as guest comment name * AO3-6017 Fix that color was set twice on the pseud * AO3-6017 Add tests for guest and anon comment mails * AO3-6017 Change anon comment case to guard clause * AO3-6017 Rubocop * AO3-6017 Apply suggestions from code review Co-authored-by: Brian Austin <13002992+brianjaustin@users.noreply.github.com> * AO3-6017 Apply suggestions from code review everywhere Would have been nice if GitHub allowed me to do that in my browser * AO3-6017 Add tests for registered user with default pseud * AO3-6017 Add previews for comment reply sent and anon comment reply * AO3-6017 Rubocop * AO3-6017 Fix copy paste error * AO3-6017 Deduplicate comment notification tests in admin mailer spec * AO3-6017 Remove mistakenly added indentation In text mails the indentation ends up in the emails, which is not desired * AO3-6017 Improve locale keys * AO3-6017 Add comments to mailer previews * AO3-6017 Merge fix --------- Co-authored-by: Brian Austin <13002992+brianjaustin@users.noreply.github.com> --- app/helpers/mailer_helper.rb | 19 +- .../comment_notification.text.erb | 2 +- .../edited_comment_notification.text.erb | 2 +- config/locales/mailers/en.yml | 7 +- spec/mailers/admin_mailer_spec.rb | 294 ++++++++++++------ spec/mailers/comment_mailer_spec.rb | 236 ++++++++++++-- test/mailers/previews/admin_mailer_preview.rb | 29 ++ .../previews/comment_mailer_preview.rb | 55 ++++ 8 files changed, 526 insertions(+), 118 deletions(-) create mode 100644 test/mailers/previews/admin_mailer_preview.rb create mode 100644 test/mailers/previews/comment_mailer_preview.rb diff --git a/app/helpers/mailer_helper.rb b/app/helpers/mailer_helper.rb index c83af00bf0f..67af73b77ec 100644 --- a/app/helpers/mailer_helper.rb +++ b/app/helpers/mailer_helper.rb @@ -181,22 +181,25 @@ def style_work_tag_metadata(tags) end def commenter_pseud_or_name_link(comment) + return style_bold(t("roles.anonymous_creator")) if comment.by_anonymous_creator? + if comment.comment_owner.nil? - t("roles.guest_commenter_name_html", name: style_bold(comment.comment_owner_name), role: style_role(t("roles.guest_with_parens"))) - elsif comment.by_anonymous_creator? - style_bold(t("roles.anonymous_creator")) + t("roles.commenter_name.html", name: style_bold(comment.comment_owner_name), role_with_parens: style_role(t("roles.guest_with_parens"))) else - style_pseud_link(comment.pseud) + role = comment.user.official ? t("roles.official_with_parens") : t("roles.registered_with_parens") + pseud_link = style_link(comment.pseud.byline, user_pseud_url(comment.user, comment.pseud)) + t("roles.commenter_name.html", name: tag.strong(pseud_link), role_with_parens: style_role(role)) end end def commenter_pseud_or_name_text(comment) + return t("roles.anonymous_creator") if comment.by_anonymous_creator? + if comment.comment_owner.nil? - t("roles.guest_commenter_name_text", name: comment.comment_owner_name, role: t("roles.guest_with_parens")) - elsif comment.by_anonymous_creator? - t("roles.anonymous_creator") + t("roles.commenter_name.text", name: comment.comment_owner_name, role_with_parens: t("roles.guest_with_parens")) else - text_pseud(comment.pseud) + role = comment.user.official ? t("roles.official_with_parens") : t("roles.registered_with_parens") + t("roles.commenter_name.text", name: text_pseud(comment.pseud), role_with_parens: role) end end diff --git a/app/views/admin_mailer/comment_notification.text.erb b/app/views/admin_mailer/comment_notification.text.erb index c5ebdddad29..3da5b5b84d9 100644 --- a/app/views/admin_mailer/comment_notification.text.erb +++ b/app/views/admin_mailer/comment_notification.text.erb @@ -1,5 +1,5 @@ <% content_for :message do %> -<%= commenter_pseud_or_name_text(@comment) %> left the following comment on +<%= commenter_pseud_or_name_text(@comment) %> left the following comment on <% if @comment.ultimate_parent.is_a?(Tag) then %>the tag "<%= @comment.ultimate_parent.commentable_name %>" (<%= url_for(:controller => :tags, :action => :show, :id => @comment.ultimate_parent, :only_path => false) %>) <% else %>"<%= @comment.ultimate_parent.commentable_name.html_safe %>" (<%= polymorphic_url(@comment.ultimate_parent, :only_path => false) %>)<% end %>: <%= text_divider %> diff --git a/app/views/admin_mailer/edited_comment_notification.text.erb b/app/views/admin_mailer/edited_comment_notification.text.erb index 26cb244e166..12768817b05 100644 --- a/app/views/admin_mailer/edited_comment_notification.text.erb +++ b/app/views/admin_mailer/edited_comment_notification.text.erb @@ -1,5 +1,5 @@ <% content_for :message do %> -<%= commenter_pseud_or_name_text(@comment) %> edited the following comment on +<%= commenter_pseud_or_name_text(@comment) %> edited the following comment on <% if @comment.ultimate_parent.is_a?(Tag) then %>the tag "<%= @comment.ultimate_parent.commentable_name %>" (<%= url_for(:controller => :tags, :action => :show, :id => @comment.ultimate_parent, :only_path => false) %>) <% else %>"<%= @comment.ultimate_parent.commentable_name.html_safe %>" (<%= polymorphic_url(@comment.ultimate_parent, :only_path => false) %>)<% end %>: <%= text_divider %> diff --git a/config/locales/mailers/en.yml b/config/locales/mailers/en.yml index 59fa550d064..b612c6b6c4b 100644 --- a/config/locales/mailers/en.yml +++ b/config/locales/mailers/en.yml @@ -83,9 +83,12 @@ en: support: The AO3 Support team roles: anonymous_creator: Anonymous Creator - guest_commenter_name_html: "%{name} %{role}" - guest_commenter_name_text: "%{name} %{role}" + commenter_name: + html: "%{name} %{role_with_parens}" + text: "%{name} %{role_with_parens}" guest_with_parens: "(Guest)" + official_with_parens: "(Official)" + registered_with_parens: "(Registered User)" user_mailer: abuse_report: copy: diff --git a/spec/mailers/admin_mailer_spec.rb b/spec/mailers/admin_mailer_spec.rb index eb73425fd43..9977bd05699 100644 --- a/spec/mailers/admin_mailer_spec.rb +++ b/spec/mailers/admin_mailer_spec.rb @@ -3,93 +3,7 @@ require "spec_helper" describe AdminMailer do - describe "#comment_notification" do - let(:email) { described_class.comment_notification(comment.id) } - - context "when the comment is on an admin post" do - let(:comment) { create(:comment, :on_admin_post) } - - context "and the comment's contents contain an image" do - let(:image_url) { "an_image.png" } - let(:image_tag) { "" } - - before do - comment.comment_content += image_tag - comment.save! - end - - context "when image safety mode is enabled for admin post comments" do - before { allow(ArchiveConfig).to receive(:PARENTS_WITH_IMAGE_SAFETY_MODE).and_return(["AdminPost"]) } - - it "strips the image from the email message but leaves its URL" do - expect(email).not_to have_html_part_content(image_tag) - expect(email).not_to have_text_part_content(image_tag) - expect(email).to have_html_part_content(image_url) - expect(email).to have_text_part_content(image_url) - end - end - - context "when image safety mode is not enabled for admin post comments" do - it "embeds the image when image safety mode is completely disabled" do - allow(ArchiveConfig).to receive(:PARENTS_WITH_IMAGE_SAFETY_MODE).and_return([]) - expect(email).to have_html_part_content(image_tag) - expect(email).not_to have_text_part_content(image_url) - end - - it "embeds the image when image safety mode is enabled for other types of comments" do - allow(ArchiveConfig).to receive(:PARENTS_WITH_IMAGE_SAFETY_MODE).and_return(%w[Chapter Tag]) - expect(email).to have_html_part_content(image_tag) - expect(email).not_to have_text_part_content(image_url) - end - end - end - end - end - - describe "#edited_comment_notification" do - let(:email) { described_class.edited_comment_notification(comment.id) } - - context "when the comment is on an admin post" do - let(:comment) { create(:comment, :on_admin_post) } - - context "with an image in the comment content" do - let(:image_url) { "an_image.png" } - let(:image_tag) { "" } - - before do - comment.comment_content += image_tag - comment.save! - end - - context "when image safety mode is enabled for admin post comments" do - before { allow(ArchiveConfig).to receive(:PARENTS_WITH_IMAGE_SAFETY_MODE).and_return(["AdminPost"]) } - - it "strips the image from the email message but leaves its URL" do - expect(email).not_to have_html_part_content(image_tag) - expect(email).not_to have_text_part_content(image_tag) - expect(email).to have_html_part_content(image_url) - expect(email).to have_text_part_content(image_url) - end - end - - context "when image safety mode is not enabled for admin post comments" do - it "embeds the image in the HTML email when image safety mode is completely disabled" do - allow(ArchiveConfig).to receive(:PARENTS_WITH_IMAGE_SAFETY_MODE).and_return([]) - expect(email).to have_html_part_content(image_tag) - expect(email).not_to have_text_part_content(image_url) - end - - it "embeds the image in the HTML email when image safety mode is enabled for other types of comments" do - allow(ArchiveConfig).to receive(:PARENTS_WITH_IMAGE_SAFETY_MODE).and_return(%w[Chapter Tag]) - expect(email).to have_html_part_content(image_tag) - expect(email).not_to have_text_part_content(image_url) - end - end - end - end - end - - describe "send_spam_alert" do + describe "#send_spam_alert" do let(:spam_user) { create(:user) } let(:spam1) do @@ -216,7 +130,7 @@ end end - describe "set_password_notification" do + describe "#set_password_notification" do subject(:email) { AdminMailer.set_password_notification(admin, token) } let(:admin) { create(:admin) } @@ -258,4 +172,208 @@ end end end + + let(:commenter) { create(:user, login: "Accumulator") } + let(:commenter_pseud) { create(:pseud, user: commenter, name: "Blueprint") } + let(:comment) { create(:comment, :on_admin_post, pseud: commenter_pseud) } + + shared_examples "a notification email with the commenter's pseud and username" do + describe "HTML email" do + it "has the pseud and username of the commenter" do + expect(email).to have_html_part_content(">Blueprint (Accumulator) (Registered User)") + expect(subject.html_part).to have_xpath( + "//a[@href=\"#{user_pseud_url(commenter, commenter_pseud)}\"]", + text: "Blueprint (Accumulator)" + ) + end + end + + describe "text email" do + it "has the pseud and username of the commenter" do + expect(subject).to have_text_part_content( + "Blueprint (Accumulator) (#{user_pseud_url(commenter, commenter_pseud)}) (Registered User)" + ) + end + end + end + + shared_examples "a notification email that marks the commenter as official" do + describe "HTML email" do + it "has the username of the commenter and the official role" do + expect(email).to have_html_part_content(">Centrifuge (Official)") + expect(subject.html_part).to have_xpath( + "//a[@href=\"#{user_pseud_url(commenter, commenter.default_pseud)}\"]", + text: "Centrifuge" + ) + end + end + + describe "text email" do + it "has the username of the commenter and the official role" do + expect(subject).to have_text_part_content( + "Centrifuge (#{user_pseud_url(commenter, commenter.default_pseud)}) (Official)" + ) + end + end + end + + shared_examples "a notification email with only the commenter's username" do + describe "HTML email" do + it "has only the username of the commenter" do + expect(email).to have_html_part_content(">Exoskeleton (Registered User)") + expect(subject.html_part).to have_xpath( + "//a[@href=\"#{user_pseud_url(commenter, commenter.default_pseud)}\"]", + text: "Exoskeleton" + ) + expect(email).not_to have_html_part_content(">Exoskeleton (Exoskeleton)") + end + end + + describe "text email" do + it "has only the username of the commenter" do + expect(subject).to have_text_part_content( + "Exoskeleton (#{user_pseud_url(commenter, commenter.default_pseud)}) (Registered User)" + ) + expect(subject).not_to have_text_part_content("Exoskeleton (Exoskeleton)") + end + end + end + + describe "#comment_notification" do + subject(:email) { AdminMailer.comment_notification(comment.id) } + + it_behaves_like "an email with a valid sender" + it_behaves_like "a multipart email" + it_behaves_like "a notification email with the commenter's pseud and username" + + context "when the comment is on an admin post" do + let(:comment) { create(:comment, :on_admin_post) } + + context "and the comment's contents contain an image" do + let(:image_url) { "an_image.png" } + let(:image_tag) { "" } + + before do + comment.comment_content += image_tag + comment.save! + end + + context "when image safety mode is enabled for admin post comments" do + before { allow(ArchiveConfig).to receive(:PARENTS_WITH_IMAGE_SAFETY_MODE).and_return(["AdminPost"]) } + + it "strips the image from the email message but leaves its URL" do + expect(email).not_to have_html_part_content(image_tag) + expect(email).not_to have_text_part_content(image_tag) + expect(email).to have_html_part_content(image_url) + expect(email).to have_text_part_content(image_url) + end + end + + context "when image safety mode is not enabled for admin post comments" do + it "embeds the image when image safety mode is completely disabled" do + allow(ArchiveConfig).to receive(:PARENTS_WITH_IMAGE_SAFETY_MODE).and_return([]) + expect(email).to have_html_part_content(image_tag) + expect(email).not_to have_text_part_content(image_url) + end + + it "embeds the image when image safety mode is enabled for other types of comments" do + allow(ArchiveConfig).to receive(:PARENTS_WITH_IMAGE_SAFETY_MODE).and_return(%w[Chapter Tag]) + expect(email).to have_html_part_content(image_tag) + expect(email).not_to have_text_part_content(image_url) + end + end + end + end + + context "when the comment is by an official user using their default pseud" do + let(:commenter) { create(:official_user, login: "Centrifuge") } + let(:comment) { create(:comment, :on_admin_post, pseud: commenter.default_pseud) } + + it_behaves_like "a notification email that marks the commenter as official" + end + + context "when the commenter is a guest" do + let(:comment) { create(:comment, :on_admin_post, pseud: nil, name: "Defender", email: Faker::Internet.email) } + + describe "HTML email" do + it "has the name of the guest and the guest role" do + expect(email).to have_html_part_content(">Defender (Guest)") + end + end + + describe "text email" do + it "has the name of the guest and the guest role" do + expect(subject).to have_text_part_content("Defender (Guest)") + end + end + end + + context "when the comment is by a registered user using their default pseud" do + let(:commenter) { create(:user, login: "Exoskeleton") } + let(:comment) { create(:comment, pseud: commenter.default_pseud) } + + it_behaves_like "a notification email with only the commenter's username" + end + end + + describe "#comment_edited_notification" do + subject(:email) { AdminMailer.edited_comment_notification(comment.id) } + + it_behaves_like "an email with a valid sender" + it_behaves_like "a multipart email" + it_behaves_like "a notification email with the commenter's pseud and username" + + context "when the comment is on an admin post" do + let(:comment) { create(:comment, :on_admin_post) } + + context "with an image in the comment content" do + let(:image_url) { "an_image.png" } + let(:image_tag) { "" } + + before do + comment.comment_content += image_tag + comment.save! + end + + context "when image safety mode is enabled for admin post comments" do + before { allow(ArchiveConfig).to receive(:PARENTS_WITH_IMAGE_SAFETY_MODE).and_return(["AdminPost"]) } + + it "strips the image from the email message but leaves its URL" do + expect(email).not_to have_html_part_content(image_tag) + expect(email).not_to have_text_part_content(image_tag) + expect(email).to have_html_part_content(image_url) + expect(email).to have_text_part_content(image_url) + end + end + + context "when image safety mode is not enabled for admin post comments" do + it "embeds the image in the HTML email when image safety mode is completely disabled" do + allow(ArchiveConfig).to receive(:PARENTS_WITH_IMAGE_SAFETY_MODE).and_return([]) + expect(email).to have_html_part_content(image_tag) + expect(email).not_to have_text_part_content(image_url) + end + + it "embeds the image in the HTML email when image safety mode is enabled for other types of comments" do + allow(ArchiveConfig).to receive(:PARENTS_WITH_IMAGE_SAFETY_MODE).and_return(%w[Chapter Tag]) + expect(email).to have_html_part_content(image_tag) + expect(email).not_to have_text_part_content(image_url) + end + end + end + end + + context "when the comment is by an official user using their default pseud" do + let(:commenter) { create(:official_user, login: "Centrifuge") } + let(:comment) { create(:comment, :on_admin_post, pseud: commenter.default_pseud) } + + it_behaves_like "a notification email that marks the commenter as official" + end + + context "when the comment is by a registered user using their default pseud" do + let(:commenter) { create(:user, login: "Exoskeleton") } + let(:comment) { create(:comment, :on_admin_post, pseud: commenter.default_pseud) } + + it_behaves_like "a notification email with only the commenter's username" + end + end end diff --git a/spec/mailers/comment_mailer_spec.rb b/spec/mailers/comment_mailer_spec.rb index f248ed79c4c..7a2c2d7456e 100644 --- a/spec/mailers/comment_mailer_spec.rb +++ b/spec/mailers/comment_mailer_spec.rb @@ -1,8 +1,10 @@ require "spec_helper" describe CommentMailer do - let(:comment) { create(:comment) } let(:user) { create(:user) } + let(:commenter) { create(:user, login: "Accumulator") } + let(:commenter_pseud) { create(:pseud, user: commenter, name: "Blueprint") } + let(:comment) { create(:comment, pseud: commenter_pseud) } shared_examples "it retries when the comment doesn't exist" do it "tries to send the email 3 times, then fails silently" do @@ -71,6 +73,82 @@ end end + shared_examples "a notification email with the commenter's pseud and username" do + describe "HTML email" do + it "has the pseud and username of the commenter" do + expect(email).to have_html_part_content(">Blueprint (Accumulator) (Registered User)") + expect(subject.html_part).to have_xpath( + "//a[@href=\"#{user_pseud_url(commenter, commenter_pseud)}\"]", + text: "Blueprint (Accumulator)" + ) + end + end + + describe "text email" do + it "has the pseud and username of the commenter" do + expect(subject).to have_text_part_content( + "Blueprint (Accumulator) (#{user_pseud_url(commenter, commenter_pseud)}) (Registered User)" + ) + end + end + end + + shared_examples "a notification email that marks the commenter as official" do + describe "HTML email" do + it "has the username of the commenter and the official role" do + expect(email).to have_html_part_content(">Centrifuge (Official)") + expect(subject.html_part).to have_xpath( + "//a[@href=\"#{user_pseud_url(commenter, commenter.default_pseud)}\"]", + text: "Centrifuge" + ) + end + end + + describe "text email" do + it "has the username of the commenter and the official role" do + expect(subject).to have_text_part_content( + "Centrifuge (#{user_pseud_url(commenter, commenter.default_pseud)}) (Official)" + ) + end + end + end + + shared_examples "a notification email that marks the commenter as a guest" do + describe "HTML email" do + it "has the name of the guest and the guest role" do + expect(email).to have_html_part_content(">Defender (Guest)") + end + end + + describe "text email" do + it "has the name of the guest and the guest role" do + expect(subject).to have_text_part_content("Defender (Guest)") + end + end + end + + shared_examples "a notification email with only the commenter's username" do + describe "HTML email" do + it "has only the username of the commenter" do + expect(email).to have_html_part_content(">Exoskeleton (Registered User)") + expect(subject.html_part).to have_xpath( + "//a[@href=\"#{user_pseud_url(commenter, commenter.default_pseud)}\"]", + text: "Exoskeleton" + ) + expect(email).not_to have_html_part_content(">Exoskeleton (Exoskeleton)") + end + end + + describe "text email" do + it "has only the username of the commenter" do + expect(subject).to have_text_part_content( + "Exoskeleton (#{user_pseud_url(commenter, commenter.default_pseud)}) (Registered User)" + ) + expect(subject).not_to have_text_part_content("Exoskeleton (Exoskeleton)") + end + end + end + shared_examples "a comment subject to image safety mode settings" do let(:image_url) { "an_image.png" } let(:image_tag) { "" } @@ -112,11 +190,33 @@ subject(:email) { CommentMailer.comment_notification(user, comment) } it_behaves_like "an email with a valid sender" + it_behaves_like "a multipart email" it_behaves_like "it retries when the comment doesn't exist" it_behaves_like "a notification email with a link to the comment" it_behaves_like "a notification email with a link to reply to the comment" + it_behaves_like "a notification email with the commenter's pseud and username" it_behaves_like "a comment subject to image safety mode settings" + context "when the comment is by an official user using their default pseud" do + let(:commenter) { create(:official_user, login: "Centrifuge") } + let(:comment) { create(:comment, pseud: commenter.default_pseud) } + + it_behaves_like "a notification email that marks the commenter as official" + end + + context "when the comment is by a guest" do + let(:comment) { create(:comment, pseud: nil, name: "Defender", email: Faker::Internet.email) } + + it_behaves_like "a notification email that marks the commenter as a guest" + end + + context "when the comment is by a registered user using their default pseud" do + let(:commenter) { create(:user, login: "Exoskeleton") } + let(:comment) { create(:comment, pseud: commenter.default_pseud) } + + it_behaves_like "a notification email with only the commenter's username" + end + context "when the comment is on an admin post" do let(:comment) { create(:comment, :on_admin_post) } @@ -124,41 +224,60 @@ end context "when the comment is a reply to another comment" do - let(:comment) { create(:comment, commentable: create(:comment)) } + let(:comment) { create(:comment, commentable: create(:comment), pseud: commenter_pseud) } it_behaves_like "a notification email with a link to the comment" it_behaves_like "a notification email with a link to reply to the comment" it_behaves_like "a notification email with a link to the comment's thread" + it_behaves_like "a notification email with the commenter's pseud and username" it_behaves_like "a comment subject to image safety mode settings" end context "when the comment is on a tag" do - let(:comment) { create(:comment, :on_tag) } + let(:comment) { create(:comment, :on_tag, pseud: commenter_pseud) } it_behaves_like "a notification email with a link to the comment" it_behaves_like "a notification email with a link to reply to the comment" + it_behaves_like "a notification email with the commenter's pseud and username" it_behaves_like "a comment subject to image safety mode settings" context "when the comment is a reply to another comment" do - let(:comment) { create(:comment, commentable: create(:comment, :on_tag)) } + let(:comment) { create(:comment, commentable: create(:comment, :on_tag), pseud: commenter_pseud) } it_behaves_like "a notification email with a link to the comment" it_behaves_like "a notification email with a link to reply to the comment" it_behaves_like "a notification email with a link to the comment's thread" + it_behaves_like "a notification email with the commenter's pseud and username" it_behaves_like "a comment subject to image safety mode settings" end end end - describe "edited_comment_notification" do + describe "#edited_comment_notification" do subject(:email) { CommentMailer.edited_comment_notification(user, comment) } it_behaves_like "an email with a valid sender" + it_behaves_like "a multipart email" it_behaves_like "it retries when the comment doesn't exist" it_behaves_like "a notification email with a link to the comment" it_behaves_like "a notification email with a link to reply to the comment" + it_behaves_like "a notification email with the commenter's pseud and username" it_behaves_like "a comment subject to image safety mode settings" + context "when the comment is by an official user using their default pseud" do + let(:commenter) { create(:official_user, login: "Centrifuge") } + let(:comment) { create(:comment, pseud: commenter.default_pseud) } + + it_behaves_like "a notification email that marks the commenter as official" + end + + context "when the comment is by a registered user using their default pseud" do + let(:commenter) { create(:user, login: "Exoskeleton") } + let(:comment) { create(:comment, pseud: commenter.default_pseud) } + + it_behaves_like "a notification email with only the commenter's username" + end + context "when the comment is on an admin post" do let(:comment) { create(:comment, :on_admin_post) } @@ -166,45 +285,70 @@ end context "when the comment is a reply to another comment" do - let(:comment) { create(:comment, commentable: create(:comment)) } + let(:comment) { create(:comment, commentable: create(:comment), pseud: commenter_pseud) } it_behaves_like "a notification email with a link to the comment" it_behaves_like "a notification email with a link to reply to the comment" it_behaves_like "a notification email with a link to the comment's thread" + it_behaves_like "a notification email with the commenter's pseud and username" it_behaves_like "a comment subject to image safety mode settings" end context "when the comment is on a tag" do - let(:comment) { create(:comment, :on_tag) } + let(:comment) { create(:comment, :on_tag, pseud: commenter_pseud) } it_behaves_like "a notification email with a link to the comment" it_behaves_like "a notification email with a link to reply to the comment" + it_behaves_like "a notification email with the commenter's pseud and username" it_behaves_like "a comment subject to image safety mode settings" context "when the comment is a reply to another comment" do - let(:comment) { create(:comment, commentable: create(:comment, :on_tag)) } + let(:comment) { create(:comment, commentable: create(:comment, :on_tag), pseud: commenter_pseud) } it_behaves_like "a notification email with a link to the comment" it_behaves_like "a notification email with a link to reply to the comment" it_behaves_like "a notification email with a link to the comment's thread" + it_behaves_like "a notification email with the commenter's pseud and username" it_behaves_like "a comment subject to image safety mode settings" end end end - describe "comment_reply_notification" do + describe "#comment_reply_notification" do subject(:email) { CommentMailer.comment_reply_notification(parent_comment, comment) } let(:parent_comment) { create(:comment) } - let(:comment) { create(:comment, commentable: parent_comment) } + let(:comment) { create(:comment, commentable: parent_comment, pseud: commenter_pseud) } it_behaves_like "an email with a valid sender" + it_behaves_like "a multipart email" it_behaves_like "it retries when the comment doesn't exist" it_behaves_like "a notification email with a link to the comment" it_behaves_like "a notification email with a link to reply to the comment" it_behaves_like "a notification email with a link to the comment's thread" + it_behaves_like "a notification email with the commenter's pseud and username" it_behaves_like "a comment subject to image safety mode settings" + context "when the comment is by an official user using their default pseud" do + let(:commenter) { create(:official_user, login: "Centrifuge") } + let(:comment) { create(:comment, commentable: parent_comment, pseud: commenter.default_pseud) } + + it_behaves_like "a notification email that marks the commenter as official" + end + + context "when the comment is by a guest" do + let(:comment) { create(:comment, commentable: parent_comment, pseud: nil, name: "Defender", email: Faker::Internet.email) } + + it_behaves_like "a notification email that marks the commenter as a guest" + end + + context "when the comment is by a registered user using their default pseud" do + let(:commenter) { create(:user, login: "Exoskeleton") } + let(:comment) { create(:comment, commentable: parent_comment, pseud: commenter.default_pseud) } + + it_behaves_like "a notification email with only the commenter's username" + end + context "when the comment is on an admin post" do let(:comment) { create(:comment, :on_admin_post) } @@ -212,11 +356,12 @@ end context "when the comment is on a tag" do - let(:parent_comment) { create(:comment, :on_tag) } + let(:parent_comment) { create(:comment, :on_tag, pseud: commenter_pseud) } it_behaves_like "a notification email with a link to the comment" it_behaves_like "a notification email with a link to reply to the comment" it_behaves_like "a notification email with a link to the comment's thread" + it_behaves_like "a notification email with the commenter's pseud and username" it_behaves_like "a comment subject to image safety mode settings" end @@ -234,21 +379,57 @@ it_behaves_like "an unsent email" end + + context "when the comment is from the author of the anonymous work" do + let(:work) { create(:work, authors: [commenter_pseud], collections: [create(:anonymous_collection)]) } + let(:parent_comment) { create(:comment, commentable: work) } + let(:comment) { create(:comment, commentable: parent_comment, pseud: commenter_pseud) } + + describe "HTML email" do + it "does not reveal the pseud of the replier" do + expect(subject).to have_html_part_content(">Anonymous Creator") + expect(email).not_to have_html_part_content(">Blueprint (Accumulator)") + end + end + + describe "text email" do + it "does not reveal the pseud of the replier" do + expect(subject).to have_text_part_content("Anonymous Creator") + expect(subject).not_to have_text_part_content("Blueprint (Accumulator)") + end + end + end end - describe "edited_comment_reply_notification" do + describe "#edited_comment_reply_notification" do subject(:email) { CommentMailer.edited_comment_reply_notification(parent_comment, comment) } let(:parent_comment) { create(:comment) } - let(:comment) { create(:comment, commentable: parent_comment) } + let(:comment) { create(:comment, commentable: parent_comment, pseud: commenter_pseud) } it_behaves_like "an email with a valid sender" + it_behaves_like "a multipart email" it_behaves_like "it retries when the comment doesn't exist" it_behaves_like "a notification email with a link to the comment" it_behaves_like "a notification email with a link to reply to the comment" it_behaves_like "a notification email with a link to the comment's thread" + it_behaves_like "a notification email with the commenter's pseud and username" it_behaves_like "a comment subject to image safety mode settings" + context "when the comment is by an official user using their default pseud" do + let(:commenter) { create(:official_user, login: "Centrifuge") } + let(:comment) { create(:comment, commentable: parent_comment, pseud: commenter.default_pseud) } + + it_behaves_like "a notification email that marks the commenter as official" + end + + context "when the comment is by a registered user using their default pseud" do + let(:commenter) { create(:user, login: "Exoskeleton") } + let(:comment) { create(:comment, commentable: parent_comment, pseud: commenter.default_pseud) } + + it_behaves_like "a notification email with only the commenter's username" + end + context "when the comment is on an admin post" do let(:comment) { create(:comment, :on_admin_post) } @@ -256,11 +437,12 @@ end context "when the comment is on a tag" do - let(:parent_comment) { create(:comment, :on_tag) } + let(:parent_comment) { create(:comment, :on_tag, pseud: commenter_pseud) } it_behaves_like "a notification email with a link to the comment" it_behaves_like "a notification email with a link to reply to the comment" it_behaves_like "a notification email with a link to the comment's thread" + it_behaves_like "a notification email with the commenter's pseud and username" it_behaves_like "a comment subject to image safety mode settings" end @@ -280,10 +462,11 @@ end end - describe "comment_sent_notification" do + describe "#comment_sent_notification" do subject(:email) { CommentMailer.comment_sent_notification(comment) } it_behaves_like "an email with a valid sender" + it_behaves_like "a multipart email" it_behaves_like "it retries when the comment doesn't exist" it_behaves_like "a notification email with a link to the comment" it_behaves_like "a comment subject to image safety mode settings" @@ -302,16 +485,18 @@ end end - describe "comment_reply_sent_notification" do + describe "#comment_reply_sent_notification" do subject(:email) { CommentMailer.comment_reply_sent_notification(comment) } - let(:parent_comment) { create(:comment) } + let(:parent_comment) { create(:comment, pseud: commenter_pseud) } let(:comment) { create(:comment, commentable: parent_comment) } it_behaves_like "an email with a valid sender" + it_behaves_like "a multipart email" it_behaves_like "it retries when the comment doesn't exist" it_behaves_like "a notification email with a link to the comment" it_behaves_like "a notification email with a link to the comment's thread" + it_behaves_like "a notification email with the commenter's pseud and username" it_behaves_like "a comment subject to image safety mode settings" context "when the comment is on an admin post" do @@ -320,11 +505,26 @@ it_behaves_like "a comment subject to image safety mode settings" end + context "when the comment is by an official user using their default pseud" do + let(:commenter) { create(:official_user, login: "Centrifuge") } + let(:parent_comment) { create(:comment, pseud: commenter.default_pseud) } + + it_behaves_like "a notification email that marks the commenter as official" # for parent comment + end + + context "when the comment is by a registered user using their default pseud" do + let(:commenter) { create(:user, login: "Exoskeleton") } + let(:parent_comment) { create(:comment, pseud: commenter.default_pseud) } + + it_behaves_like "a notification email with only the commenter's username" # for parent comment + end + context "when the parent comment is on a tag" do - let(:parent_comment) { create(:comment, :on_tag) } + let(:parent_comment) { create(:comment, :on_tag, pseud: commenter_pseud) } it_behaves_like "a notification email with a link to the comment" it_behaves_like "a notification email with a link to the comment's thread" + it_behaves_like "a notification email with the commenter's pseud and username" # for parent comment it_behaves_like "a comment subject to image safety mode settings" end end diff --git a/test/mailers/previews/admin_mailer_preview.rb b/test/mailers/previews/admin_mailer_preview.rb new file mode 100644 index 00000000000..3d3da7ab8cc --- /dev/null +++ b/test/mailers/previews/admin_mailer_preview.rb @@ -0,0 +1,29 @@ +class AdminMailerPreview < ApplicationMailerPreview + # Sent to an admin when they get a comment on an admin post + def comment_notification + commenter = create(:user, login: "Accumulator") + commenter_pseud = create(:pseud, user: commenter, name: "Blueprint") + comment = create(:comment, pseud: commenter_pseud) + AdminMailer.comment_notification(comment.id) + end + + # Sent to an admin when they get a comment on an admin post by an official user + def comment_notification_official + commenter = create(:official_user, login: "Centrifuge") + comment = create(:comment, pseud: commenter.default_pseud) + AdminMailer.comment_notification(comment.id) + end + + # Sent to an admin when they get a comment on an admin post by a guest + def comment_notification_guest + comment = create(:comment, :by_guest) + AdminMailer.comment_notification(comment.id) + end + + # Sent to an admin when a comment on an admin post is edited + def edited_comment_notification + commenter = create(:user, login: "Defender") + comment = create(:comment, pseud: commenter.default_pseud) + AdminMailer.edited_comment_notification(comment.id) + end +end diff --git a/test/mailers/previews/comment_mailer_preview.rb b/test/mailers/previews/comment_mailer_preview.rb new file mode 100644 index 00000000000..167e75e8560 --- /dev/null +++ b/test/mailers/previews/comment_mailer_preview.rb @@ -0,0 +1,55 @@ +class CommentMailerPreview < ApplicationMailerPreview + # Sent to a user when they get a comment on a top-level creation + def comment_notification + user = create(:user) + + commenter = create(:user, login: "Accumulator") + commenter_pseud = create(:pseud, user: commenter, name: "Blueprint") + comment = create(:comment, pseud: commenter_pseud) + CommentMailer.comment_notification(user, comment) + end + + # Sent to a user when they get a comment on a top-level creation by an official user + def comment_notification_official + user = create(:user) + + commenter = create(:official_user, login: "Centrifuge") + comment = create(:comment, pseud: commenter.default_pseud) + CommentMailer.comment_notification(user, comment) + end + + # Sent to a user when they get a comment on a top-level creation by a guest + def comment_notification_guest + user = create(:user) + comment = create(:comment, :by_guest) + CommentMailer.comment_notification(user, comment) + end + + # Sent to a user when they get a comment reply to their comment + def comment_reply_notification + comment = create(:comment) + + replier = create(:user, login: "Defender") + reply = create(:comment, commentable: comment, pseud: replier.default_pseud) + CommentMailer.comment_reply_notification(comment, reply) + end + + # Sent to a user when they get a reply to their comment by an anonymous creator + def comment_reply_notification_anon + replier = create(:user) + work = create(:work, authors: [replier.default_pseud], collections: [create(:anonymous_collection)]) + + comment = create(:comment, commentable: work) + reply = create(:comment, commentable: comment, pseud: replier.default_pseud) + CommentMailer.comment_reply_notification(comment, reply) + end + + # Sent to a user when they make a reply to a comment and they want to be notified of their own comments + def comment_reply_sent_notification + commenter = create(:user, login: "Exoskeleton") + + comment = create(:comment, pseud: commenter.default_pseud) + reply = create(:comment, commentable: comment) + CommentMailer.comment_reply_sent_notification(reply) + end +end From c23be27b47d9e6f47a989464be88445dcdd86b8e Mon Sep 17 00:00:00 2001 From: james_ <1606304+zz9pzza@users.noreply.github.com> Date: Fri, 17 May 2024 17:31:21 +0100 Subject: [PATCH 010/181] AO3-6725 remove get recent bookmarks (#4808) * Hopefully the tests should fail * Found more bits * And remove the failing test * Listen to the hound * Remove uneeded div * Follow Sarkens instructions (I hope) --------- Co-authored-by: 1606304+zz9pzza@users.noreply.github.com --- app/controllers/bookmarks_controller.rb | 24 ++----------------- app/views/bookmarks/_bookmark_blurb.html.erb | 13 ---------- app/views/bookmarks/_bookmarks.html.erb | 9 +------ app/views/bookmarks/fetch_recent.js.erb | 8 ------- app/views/bookmarks/hide_recent.js.erb | 3 --- config/routes.rb | 3 --- features/bookmarks/bookmark_create.feature | 21 ---------------- .../low_vision_default_site_screen_.css | 8 +------ .../stylesheets/site/2.0/13-group-blurb.css | 7 +----- 9 files changed, 5 insertions(+), 91 deletions(-) delete mode 100644 app/views/bookmarks/fetch_recent.js.erb delete mode 100644 app/views/bookmarks/hide_recent.js.erb diff --git a/app/controllers/bookmarks_controller.rb b/app/controllers/bookmarks_controller.rb index ee48b66edbd..32658a750d1 100644 --- a/app/controllers/bookmarks_controller.rb +++ b/app/controllers/bookmarks_controller.rb @@ -1,10 +1,10 @@ class BookmarksController < ApplicationController before_action :load_collection before_action :load_owner, only: [:index] - before_action :load_bookmarkable, only: [:index, :new, :create, :fetch_recent, :hide_recent] + before_action :load_bookmarkable, only: [:index, :new, :create] before_action :users_only, only: [:new, :create, :edit, :update] before_action :check_user_status, only: [:new, :create, :edit, :update] - before_action :load_bookmark, only: [:show, :edit, :update, :destroy, :fetch_recent, :hide_recent, :confirm_delete, :share] + before_action :load_bookmark, only: [:show, :edit, :update, :destroy, :confirm_delete, :share] before_action :check_visibility, only: [:show, :share] before_action :check_ownership, only: [:edit, :update, :destroy, :confirm_delete, :share] @@ -279,26 +279,6 @@ def destroy redirect_to user_bookmarks_path(current_user) end - # Used on index page to show 4 most recent bookmarks (after bookmark being currently viewed) via RJS - # Only main bookmarks page or tag bookmarks page - # non-JS fallback should be to the 'view all bookmarks' link which serves the same function - def fetch_recent - @bookmarkable = @bookmark.bookmarkable - respond_to do |format| - format.js { - @bookmarks = @bookmarkable.bookmarks.order_by_created_at.visible.offset(1).limit(4) - set_own_bookmarks - } - format.html do - id_symbol = (@bookmarkable.class.to_s.underscore + '_id').to_sym - redirect_to url_for({action: :index, id_symbol => @bookmarkable}) - end - end - end - def hide_recent - @bookmarkable = @bookmark.bookmarkable - end - protected def load_owner diff --git a/app/views/bookmarks/_bookmark_blurb.html.erb b/app/views/bookmarks/_bookmark_blurb.html.erb index 6f00a314dd9..8a35ee3da27 100644 --- a/app/views/bookmarks/_bookmark_blurb.html.erb +++ b/app/views/bookmarks/_bookmark_blurb.html.erb @@ -31,16 +31,6 @@ <% end %>
  • <% end %> - - <% if bookmark_count > 1 %> - <% if params[:tag_id] || (@owner.blank? && @bookmarkable.blank?) %> - - <% end %> - <% end %> <% end %> @@ -50,9 +40,6 @@ <%= render "bookmarks/bookmark_user_module", bookmark: bookmark %> - <% # recent bookmarks will be loaded up here if requested %> -
    " style="display: none;">
    - <% if logged_in_as_admin? && bookmarkable.class == ExternalWork %> <%= render "admin/admin_options", item: bookmarkable %> <% end %> diff --git a/app/views/bookmarks/_bookmarks.html.erb b/app/views/bookmarks/_bookmarks.html.erb index b6767c79d95..75f197a3f04 100644 --- a/app/views/bookmarks/_bookmarks.html.erb +++ b/app/views/bookmarks/_bookmarks.html.erb @@ -7,14 +7,7 @@ <%= render 'series/series_blurb', :series => @bookmarkable %> <% end %> -<% if params[:show_recent] %> - <% # The javascript expanded view that shows more bookmarks for a particular work on an aggregated view %> - <% # because this isn't rendered inside the index view we need to put a ul around it %> - - -<% elsif @bookmarkable %> +<% if @bookmarkable %> <% # show only partial view for each bookmark since the bookmarked object is already shown above %> <%= render partial: 'bookmarks/bookmark_blurb_short', collection: @bookmarks, as: :bookmark %> diff --git a/app/views/bookmarks/fetch_recent.js.erb b/app/views/bookmarks/fetch_recent.js.erb deleted file mode 100644 index 11b38a5a70b..00000000000 --- a/app/views/bookmarks/fetch_recent.js.erb +++ /dev/null @@ -1,8 +0,0 @@ -$j('#recent_<%= "#{@bookmark.bookmarkable_type.underscore}_#{@bookmark.id}"%>').html('<%= escape_javascript(render "bookmarks", :params => {:show_recent => true}) %>').slideDown(); -<% if @bookmark.bookmarkable.bookmarks.size > 5 %> - $j('#recent_<%= "#{@bookmark.bookmarkable_type.underscore}_#{@bookmark.id}"%>').append('<%= escape_javascript(link_to(ts("All Bookmarks"), eval(@bookmark.bookmarkable.class.to_s.underscore + "_bookmarks_path(@bookmark.bookmarkable)"), :class => "action")) %>'); - $j('#recent_<%= "#{@bookmark.bookmarkable_type.underscore}_#{@bookmark.id}"%>').append(" "); -<% end %> -$j('#recent_<%= "#{@bookmark.bookmarkable_type.underscore}_#{@bookmark.id}"%>').append('<%= escape_javascript(link_to(ts("Hide Most Recent Bookmarks"), hide_recent_bookmarks_path(@bookmark), :method => :get, :remote => true, :class => "action")) %>'); -$j('#recent_link_<%= "#{@bookmark.bookmarkable_type.underscore}_#{@bookmark.id}"%>').html('<%= escape_javascript(link_to(ts("Hide Most Recent Bookmarks"), hide_recent_bookmarks_path(@bookmark), :method => :get, :remote => true, :class => "action")) %>'); -$j('#recent_<%= "#{@bookmark.bookmarkable_type.underscore}_#{@bookmark.id}"%>').slideDown(); diff --git a/app/views/bookmarks/hide_recent.js.erb b/app/views/bookmarks/hide_recent.js.erb deleted file mode 100644 index 4eaf03574b1..00000000000 --- a/app/views/bookmarks/hide_recent.js.erb +++ /dev/null @@ -1,3 +0,0 @@ -$j('#recent_<%= "#{@bookmark.bookmarkable_type.underscore}_#{@bookmark.id}"%>').slideUp(); -$j('#recent_<%= "#{@bookmark.bookmarkable_type.underscore}_#{@bookmark.id}"%>').html(""); -$j('#recent_link_<%= "#{@bookmark.bookmarkable_type.underscore}_#{@bookmark.id}"%>').html('<%= escape_javascript(link_to(ts("Show Most Recent Bookmarks"), fetch_recent_bookmarks_path(@bookmark, fetch: true), :method => :get, :remote => true)) %>'); diff --git a/config/routes.rb b/config/routes.rb index 9927d21ea1a..fec2ca6d7c3 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -649,9 +649,6 @@ # can be refactored to not rely on their existence. # # Note written on August 1, 2017 during upgrade to Rails 5.1. - get '/bookmarks/fetch_recent/:id' => 'bookmarks#fetch_recent', as: :fetch_recent_bookmarks - get '/bookmarks/hide_recent/:id' => 'bookmarks#hide_recent', as: :hide_recent_bookmarks - get '/invite_requests/show' => 'invite_requests#show', as: :show_invite_request get '/user_invite_requests/update' => 'user_invite_requests#update' diff --git a/features/bookmarks/bookmark_create.feature b/features/bookmarks/bookmark_create.feature index e56da0f057c..56872fb0d48 100644 --- a/features/bookmarks/bookmark_create.feature +++ b/features/bookmarks/bookmark_create.feature @@ -410,27 +410,6 @@ Scenario: I cannot edit an existing bookmark to transfer it to a pseud I don't o Then I should not see "Bookmark was successfully updated" And I should see "You can't bookmark with that pseud." -@javascript -Scenario: Can use "Show Most Recent Bookmarks" from the bookmarks page - Given the work "Popular Work" - And I am logged in as "bookmarker1" - And I bookmark the work "Popular Work" with the note "Love it" - And I log out - And I am logged in as "bookmarker2" - And I bookmark the work "Popular Work" - And the statistics for the work "Popular Work" are updated - When I am on the bookmarks page - # Follow the link for bookmarker2's bookmark, which is more recent. - And I follow "Show Most Recent Bookmarks" within ".bookmark.blurb:first-child" - Then I should see "bookmarker1" within ".bookmark.blurb:first-child .recent" - And I should see "Love it" within ".bookmark.blurb:first-child .recent" - And I should see "Hide Most Recent Bookmarks" within ".bookmark.blurb:first-child .recent" - When I follow "Hide Most Recent Bookmarks" within ".bookmark.blurb:first-child .recent" - # .recent has been hidden, we should not see its contents anymore. - Then I should not see "bookmarker1" within ".bookmark.blurb:first-child" - And I should not see "Love it" within ".bookmark.blurb:first-child" - And I should see "Show Most Recent Bookmarks" within ".bookmark.blurb:first-child" - Scenario: A bookmark with duplicate tags other than capitalization has only first version of tag saved Given I am logged in as "bookmark_user" When I post the work "Revenge of the Sith" diff --git a/public/stylesheets/masters/low_vision_default/low_vision_default_site_screen_.css b/public/stylesheets/masters/low_vision_default/low_vision_default_site_screen_.css index 91877eb07fd..17bfbf566a3 100644 --- a/public/stylesheets/masters/low_vision_default/low_vision_default_site_screen_.css +++ b/public/stylesheets/masters/low_vision_default/low_vision_default_site_screen_.css @@ -37,8 +37,7 @@ form blockquote.userstuff { #inner .filters, #footer, .filters dt, -media .listbox, -.bookmark .recent .index { +media .listbox { width: 100%; } @@ -159,8 +158,3 @@ pre { background: #ccc; color: #111; } - -.bookmark .recent, -.bookmark .recent .index { - border: none; -} diff --git a/public/stylesheets/site/2.0/13-group-blurb.css b/public/stylesheets/site/2.0/13-group-blurb.css index e1b43eda370..63d317989d6 100644 --- a/public/stylesheets/site/2.0/13-group-blurb.css +++ b/public/stylesheets/site/2.0/13-group-blurb.css @@ -332,7 +332,7 @@ blurbs on the manage collection items pages, mostly reseting styles inherited fr top: 28px; } -.bookmark div.user, .bookmark div.recent { +.bookmark div.user { clear: right; box-sizing: border-box; } @@ -398,11 +398,6 @@ blurbs on the manage collection items pages, mostly reseting styles inherited fr position: static; } -.bookmark .recent .index { - float: none; - width: 100%; -} - /* mod: READING */ .reading .user { From 00a6ac21de9fa6fad3f10b7b2b9e3c697519fe22 Mon Sep 17 00:00:00 2001 From: Bilka Date: Fri, 17 May 2024 18:39:27 +0200 Subject: [PATCH 011/181] AO3-6708 Add indices to owned set taggings (#4789) --- ...20240404183910_add_indices_to_owned_set_taggings.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 db/migrate/20240404183910_add_indices_to_owned_set_taggings.rb diff --git a/db/migrate/20240404183910_add_indices_to_owned_set_taggings.rb b/db/migrate/20240404183910_add_indices_to_owned_set_taggings.rb new file mode 100644 index 00000000000..c31d8e478e9 --- /dev/null +++ b/db/migrate/20240404183910_add_indices_to_owned_set_taggings.rb @@ -0,0 +1,10 @@ +class AddIndicesToOwnedSetTaggings < ActiveRecord::Migration[6.1] + def change + change_table :owned_set_taggings do |t| + t.index :owned_tag_set_id + t.index [:set_taggable_id, :set_taggable_type, :owned_tag_set_id], + name: :index_owned_set_taggings_on_set_taggable_and_tag_set, + unique: true + end + end +end From 225462482603170ed2ff501243f7f8b8ad87cc40 Mon Sep 17 00:00:00 2001 From: Brian Austin <13002992+brianjaustin@users.noreply.github.com> Date: Fri, 17 May 2024 16:46:46 -0400 Subject: [PATCH 012/181] AO3-6706 Support Sentinel config for Redis clients (#4787) * AO3-6706 Support Sentinel config for Redis clients * Fix examples --- .../initializers/gem-plugin_config/redis.rb | 42 ++++++++++++------- .../initializers/gem-plugin_config/resque.rb | 20 ++++----- 2 files changed, 37 insertions(+), 25 deletions(-) diff --git a/config/initializers/gem-plugin_config/redis.rb b/config/initializers/gem-plugin_config/redis.rb index 82afbbf2bcd..0b6451e5e73 100644 --- a/config/initializers/gem-plugin_config/redis.rb +++ b/config/initializers/gem-plugin_config/redis.rb @@ -1,22 +1,34 @@ -require 'redis_test_setup' -include RedisTestSetup +require "redis_test_setup" +include RedisTestSetup # rubocop:disable Style/MixinUsage -rails_root = ENV["RAILS_ROOT"] || File.dirname(__FILE__) + "/../../.." -rails_env = ENV["RAILS_ENV"] || "development" +rails_root = ENV["RAILS_ROOT"] || "#{File.dirname(__FILE__)}/../../.." +rails_env = (ENV["RAILS_ENV"] || "development").to_sym -unless ENV["CI"] - if rails_env == "test" && !ENV["DOCKER"] - # https://gist.github.com/441072 - start_redis!(rails_root, :cucumber) - end -end +# https://gist.github.com/441072 +start_redis!(rails_root, :cucumber) if rails_env == "test" && !(ENV["CI"] || ENV["DOCKER"]) -redis_configs = YAML.load_file(rails_root + '/config/redis.yml') +redis_configs = YAML.load_file("#{rails_root}/config/redis.yml", symbolize_names: true) redis_configs.each_pair do |name, redis_config| - redis_host, redis_port = redis_config[rails_env].split(":") - redis_connection = Redis.new(host: redis_host, port: redis_port) - if ENV['DEV_USER'] - namespaced_redis = Redis::Namespace.new(ENV['DEV_USER'], redis: redis_connection) + redis_options = {} + if redis_config[rails_env].is_a?(Hash) + # example: + # redis_kudos: + # development + # name: redis_kudos + # sentinels: + # - host: 127.0.0.1 + # port: 26379 + # - host: 127.0.0.1 + # port: 26380 + redis_options = redis_config[rails_env] + else + redis_host, redis_port = redis_config[rails_env].split(":") + redis_options[:host] = redis_host + redis_options[:port] = redis_port + end + redis_connection = Redis.new(redis_options) + if ENV["DEV_USER"] + namespaced_redis = Redis::Namespace.new(ENV["DEV_USER"], redis: redis_connection) redis_connection = namespaced_redis end Object.const_set(name.upcase, redis_connection) diff --git a/config/initializers/gem-plugin_config/resque.rb b/config/initializers/gem-plugin_config/resque.rb index 2fc38018be9..65062890629 100644 --- a/config/initializers/gem-plugin_config/resque.rb +++ b/config/initializers/gem-plugin_config/resque.rb @@ -1,14 +1,14 @@ -require 'resque' +require "resque" -rails_root = ENV["RAILS_ROOT"] || File.dirname(__FILE__) + "/../../.." -rails_env = ENV["RAILS_ENV"] || "development" +rails_root = ENV["RAILS_ROOT"] || "#{File.dirname(__FILE__)}/../../.." +rails_env = (ENV["RAILS_ENV"] || "development").to_sym - redis_configs = YAML.load_file(rails_root + '/config/redis.yml') - Resque.redis = redis_configs['redis_resque'][rails_env] +redis_configs = YAML.load_file("#{rails_root}/config/redis.yml", symbolize_names: true) +Resque.redis = redis_configs[:redis_resque][rails_env] - # in-process performing of jobs (for testing) doesn't require a redis server - Resque.inline = ENV['RAILS_ENV'] == "test" +# in-process performing of jobs (for testing) doesn't require a redis server +Resque.inline = ENV["RAILS_ENV"] == "test" - Resque.after_fork do - Resque.redis.client.reconnect - end +Resque.after_fork do + Resque.redis.client.reconnect +end From 135caebf87473b7bc251f226ee4b7e270a59578d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20B?= Date: Sun, 19 May 2024 05:47:19 +0200 Subject: [PATCH 013/181] AO3-5860 Update tests for error message when commenting as admin (#4813) * AO3-5860 Error message when commenting as admin Do the admin check first * Also prioritize admin warning in view * Rabbit hole of behavior changes * Revert Let's try again with less behavior changes * Just update tests --- .../comments_and_kudos/add_comment.feature | 4 ++-- spec/controllers/comments_controller_spec.rb | 20 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/features/comments_and_kudos/add_comment.feature b/features/comments_and_kudos/add_comment.feature index 22eb9f46dc3..0ca9036013a 100644 --- a/features/comments_and_kudos/add_comment.feature +++ b/features/comments_and_kudos/add_comment.feature @@ -227,7 +227,7 @@ Scenario: Users with different time zone preferences should see the time in thei Scenario: Cannot comment (no form) while logged as admin - Given the work "Generic Work" + Given the work "Generic Work" by "creator" with guest comments enabled And I am logged in as an admin And I view the work "Generic Work" Then I should see "Generic Work" @@ -237,7 +237,7 @@ Scenario: Cannot comment (no form) while logged as admin Scenario: Cannot reply to comments (no button) while logged as admin - Given the work "Generic Work" + Given the work "Generic Work" by "creator" with guest comments enabled When I am logged in as "commenter" And I view the work "Generic Work" And I post a comment "Woohoo" diff --git a/spec/controllers/comments_controller_spec.rb b/spec/controllers/comments_controller_spec.rb index 784dc97d8b5..ef8179107a1 100644 --- a/spec/controllers/comments_controller_spec.rb +++ b/spec/controllers/comments_controller_spec.rb @@ -127,6 +127,15 @@ expect(flash[:error]).to be_nil it_redirects_to(chapter_path(comment.commentable, show_comments: true, anchor: "comment_#{comment.id}")) end + + context "when logged in as an admin" do + before { fake_login_admin(create(:admin)) } + + it "redirects to root with notice prompting log out" do + get :add_comment_reply, params: { comment_id: comment.id } + it_redirects_to_with_notice(root_path, "Please log out of your admin account first!") + end + end end context "guest comments are turned off in admin settings" do @@ -174,15 +183,6 @@ it_redirects_to_with_error(work_path(work), "Sorry, this work doesn't allow comments.") end end - - context "when logged in as an admin" do - before { fake_login_admin(create(:admin)) } - - it "redirects to root with notice prompting log out" do - get :add_comment_reply, params: { comment_id: comment.id } - it_redirects_to_with_notice(root_path, "Please log out of your admin account first!") - end - end end shared_examples "guest cannot reply to a user with guest replies disabled" do @@ -641,7 +641,7 @@ end context "when logged in as an admin" do - let(:work) { create(:work) } + let(:work) { create(:work, :guest_comments_on) } before { fake_login_admin(create(:admin)) } From 9d9c832d23192d5c0c936ad6a2b091f55b74587e Mon Sep 17 00:00:00 2001 From: Brian Austin <13002992+brianjaustin@users.noreply.github.com> Date: Sat, 18 May 2024 23:48:55 -0400 Subject: [PATCH 014/181] AO3-5351 Make remaining text in feedback response i18n-able (#4788) * AO3-5351 Make remaining text in feedback response i18n-able * HTML escape email text * Remove apostrophes from cucumber * Add tests * Update preview name --- app/views/user_mailer/feedback.html.erb | 13 ++----------- app/views/user_mailer/feedback.text.erb | 8 ++------ config/locales/mailers/en.yml | 3 +++ features/other_b/support.feature | 3 ++- spec/mailers/user_mailer_spec.rb | 2 ++ test/mailers/previews/user_mailer_preview.rb | 2 +- 6 files changed, 12 insertions(+), 19 deletions(-) diff --git a/app/views/user_mailer/feedback.html.erb b/app/views/user_mailer/feedback.html.erb index d116ec5409c..ba064d993af 100644 --- a/app/views/user_mailer/feedback.html.erb +++ b/app/views/user_mailer/feedback.html.erb @@ -8,20 +8,11 @@ <% end %>

    -

    - We're working hard to reply to everyone, and we'll respond to you as soon as we can. - Your communication is greatly valued, and it will be reviewed and - answered by our volunteer Support team. In the meantime, here is a copy of - the information you submitted through the Technical Support and Feedback - form: -

    +

    <%= t(".introduction") %>

    <%= style_quote("#{raw(strip_images(@summary))} #{raw(strip_images(@comment, keep_src: true))}") %> -

    - If you have additional questions or information, do not hesitate to send in - another ticket. -

    +

    <%= t(".additional_ticket") %>

    <%= t("mailer.general.closing.formal") %>
    diff --git a/app/views/user_mailer/feedback.text.erb b/app/views/user_mailer/feedback.text.erb index cfa8ba0c942..3d5b6231786 100644 --- a/app/views/user_mailer/feedback.text.erb +++ b/app/views/user_mailer/feedback.text.erb @@ -6,10 +6,7 @@ <%= t("mailer.general.greeting.informal.unaddressed") %> <% end %> -We're working hard to reply to everyone, and we'll respond to you as soon as we can. -Your communication is greatly valued, and it will be reviewed and answered -by our volunteer Support team. In the meantime, here is a copy of the -information you submitted through the Technical Support and Feedback form: +<%= t(".introduction") %> <%= text_divider %> @@ -18,8 +15,7 @@ information you submitted through the Technical Support and Feedback form: <%= text_divider %> -If you have additional questions or information, do not hesitate to send in -another ticket. +<%= t(".additional_ticket") %> <%= t("mailer.general.closing.formal") %> <%= t("mailer.general.signature.support") %> diff --git a/config/locales/mailers/en.yml b/config/locales/mailers/en.yml index b612c6b6c4b..3ca6f63d45f 100644 --- a/config/locales/mailers/en.yml +++ b/config/locales/mailers/en.yml @@ -313,6 +313,9 @@ en: text: If you have questions, please %{support} (%{url}). subject: "[%{app_name}] Your work has been deleted" support: contact Support + feedback: + additional_ticket: If you have additional questions or information, do not hesitate to send in another ticket. + introduction: 'We''re working hard to reply to everyone, and we''ll respond to you as soon as we can. Your communication is greatly valued, and it will be reviewed and answered by our volunteer Support team. In the meantime, here is a copy of the information you submitted through the Technical Support and Feedback form:' invitation: been_invited: You've been invited to join the Archive of Our Own! features: With an account, you can post fanworks, use bookmarks to keep track of works you enjoyed, receive subscription emails when your favorite creators or works update, customize the way the site looks for you, and more! diff --git a/features/other_b/support.feature b/features/other_b/support.feature index 3da8e410210..060c38db00e 100644 --- a/features/other_b/support.feature +++ b/features/other_b/support.feature @@ -16,7 +16,8 @@ Feature: Filing a support request And I press "Send" Then I should see "Your message was sent to the Archive team - thank you!" And 1 email should be delivered - And the email should contain "We're working hard to reply to everyone, and we'll respond to you as soon as we can." + And the email should contain "working hard to reply to everyone" + And the email should contain "respond to you as soon as we can." And the email should contain "If you have additional questions or information" And the email should contain "Sent at Mon, 14 Mar 2022 12:00:00 \+0000" When I follow "Support & Feedback" diff --git a/spec/mailers/user_mailer_spec.rb b/spec/mailers/user_mailer_spec.rb index 8989d34f966..13324a80cd2 100644 --- a/spec/mailers/user_mailer_spec.rb +++ b/spec/mailers/user_mailer_spec.rb @@ -703,6 +703,7 @@ # Test both body contents it_behaves_like "a multipart email" + it_behaves_like "a translated email" describe "HTML version" do it "has the correct content" do @@ -734,6 +735,7 @@ # Test both body contents it_behaves_like "a multipart email" + it_behaves_like "a translated email" describe "HTML version" do it "has the correct content" do diff --git a/test/mailers/previews/user_mailer_preview.rb b/test/mailers/previews/user_mailer_preview.rb index 006547dea1e..ebf7fd87531 100644 --- a/test/mailers/previews/user_mailer_preview.rb +++ b/test/mailers/previews/user_mailer_preview.rb @@ -24,7 +24,7 @@ def creatorship_request end # Sent to a user when the submit a support request (AKA feedback) - def feedback_response + def feedback feedback = create(:feedback) UserMailer.feedback(feedback.id) end From 47cc03880769f4256456598e94eed99a8d6e0faf Mon Sep 17 00:00:00 2001 From: sarken Date: Sun, 19 May 2024 18:00:41 -0400 Subject: [PATCH 015/181] AO3-6713 Enable comment moderation for admin posts (#4790) * Admin post comment moderation TODO: Allow admins to approve all comments on admin posts * Guess who forgot the migration * Allow admins to approve all unreviewed comments on admin posts * Cucumber tests for moderating admin post comments * Specs for admin post comment moderation * Normalize locale file * AO3-6713 Code style fixes * AO3-6713 Code style again * AO3-6713 Actually previous was fix for i18n-tasks-use not playing nice with relative keyes and this is a code style fix * AO3-6713 More style fixes because it really just wants me to rewrite this entire action * AO3-6713 Give the reviewdog what it wants (aka fuck it we remove the code that we supposedly shouldn't need) * AO3-6713 More code style * AO3-6713 Update feature test to check that I didn't break a redirect when refactoring the create action * AO3-6713 Put some changes in and see what Rubocop says * AO3-6713 Put in the other changes I thought were in the last commit * AO3-6713 Silence Reviewdog's complaints about redundant safe navigation in policies * AO3-6713 News post instead of admin post * AO3-6713 Add missing colon --- .rubocop.yml | 6 + app/controllers/admin_posts_controller.rb | 3 +- app/controllers/comments_controller.rb | 70 ++--- app/helpers/comments_helper.rb | 6 + app/policies/comment_policy.rb | 10 + .../admin_posts/_admin_post_form.html.erb | 6 + app/views/admin_posts/show.html.erb | 33 ++- app/views/comments/_comment_actions.html.erb | 2 +- app/views/comments/_comment_form.html.erb | 5 +- app/views/comments/unreviewed.html.erb | 15 +- config/locales/controllers/en.yml | 6 + config/locales/views/en.yml | 27 +- config/routes.rb | 7 +- ...rated_commenting_enabled_to_admin_posts.rb | 5 + features/admins/admin_post_news.feature | 52 ++++ features/step_definitions/admin_steps.rb | 2 +- features/support/paths.rb | 6 + spec/controllers/comments_controller_spec.rb | 261 ++++++++++++++---- 18 files changed, 412 insertions(+), 110 deletions(-) create mode 100644 db/migrate/20240303042804_add_moderated_commenting_enabled_to_admin_posts.rb diff --git a/.rubocop.yml b/.rubocop.yml index 6ca41445023..e26cf71fe7a 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -53,6 +53,12 @@ Lint/AmbiguousBlockAssociation: Lint/AmbiguousRegexpLiteral: Enabled: false +Lint/RedundantSafeNavigation: + Exclude: + # Take a better safe than sorry approach to safe navigation in admin + # policies. + - 'app/policies/*.rb' + Metrics/AbcSize: Enabled: false diff --git a/app/controllers/admin_posts_controller.rb b/app/controllers/admin_posts_controller.rb index 92637ca4657..426c71b3d3e 100644 --- a/app/controllers/admin_posts_controller.rb +++ b/app/controllers/admin_posts_controller.rb @@ -94,7 +94,8 @@ def load_languages def admin_post_params params.require(:admin_post).permit( - :admin_id, :title, :content, :translated_post_id, :language_id, :tag_list, :comment_permissions + :admin_id, :title, :content, :translated_post_id, :language_id, :tag_list, + :comment_permissions, :moderated_commenting_enabled ) end end diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index b7c8a3df1b7..7cd1f95b8d2 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -343,36 +343,34 @@ def create # First, try saving the comment if @comment.save - if @comment.approved? - if @comment.unreviewed? - flash[:comment_notice] = ts("Your comment was received! It will appear publicly after the work creator has approved it.") - else - flash[:comment_notice] = ts("Comment created!") - end - respond_to do |format| - format.html do - if request.referer&.match(/inbox/) - redirect_to user_inbox_path(current_user, filters: filter_params[:filters], page: params[:page]) - elsif request.referer&.match(/new/) - # came here from the new comment page, probably via download link - # so go back to the comments page instead of reloading full work - redirect_to comment_path(@comment) - elsif request.referer == "#{root_url}" - # replying on the homepage - redirect_to root_path - elsif @comment.unreviewed? && current_user - redirect_to comment_path(@comment) - elsif @comment.unreviewed? - redirect_to_all_comments(@commentable) - else - redirect_to_comment(@comment, {view_full_work: (params[:view_full_work] == "true"), page: params[:page]}) - end + flash[:comment_notice] = if @comment.unreviewed? + # i18n-tasks-use t("comments.create.success.moderated.admin_post") + # i18n-tasks-use t("comments.create.success.moderated.work") + t("comments.create.success.moderated.#{@comment.ultimate_parent.model_name.i18n_key}") + else + t("comments.create.success.not_moderated") + end + respond_to do |format| + format.html do + if request.referer&.match(/inbox/) + redirect_to user_inbox_path(current_user, filters: filter_params[:filters], page: params[:page]) + elsif request.referer&.match(/new/) || (@comment.unreviewed? && current_user) + # If the referer is the new comment page, go to the comment's page + # instead of reloading the full work. + # If the comment is unreviewed and commenter is logged in, take + # them to the comment's page so they can access the edit and + # delete options for the comment, since unreviewed comments don't + # appear on the commentable. + redirect_to comment_path(@comment) + elsif request.referer == root_url + # replying on the homepage + redirect_to root_path + elsif @comment.unreviewed? + redirect_to_all_comments(@commentable) + else + redirect_to_comment(@comment, { view_full_work: (params[:view_full_work] == "true"), page: params[:page] }) end end - else - # this shouldn't come up any more - flash[:comment_notice] = ts("Sorry, but this comment looks like spam to us.") - redirect_back_or_default(root_path) end else flash[:error] = ts("Couldn't save comment!") @@ -426,10 +424,17 @@ def destroy end def review - return unless @comment && current_user_owns?(@comment.ultimate_parent) && @comment.unreviewed? + if logged_in_as_admin? + authorize @comment + else + return unless current_user_owns?(@comment.ultimate_parent) + end + + return unless @comment&.unreviewed? + @comment.toggle!(:unreviewed) # mark associated inbox comments as read - InboxComment.where(user_id: current_user.id, feedback_comment_id: @comment.id).update_all(read: true) + InboxComment.where(user_id: current_user.id, feedback_comment_id: @comment.id).update_all(read: true) unless logged_in_as_admin? flash[:notice] = ts("Comment approved.") respond_to do |format| format.html do @@ -437,6 +442,8 @@ def review redirect_to user_inbox_path(current_user, page: params[:page], filters: filter_params[:filters]) elsif params[:approved_from] == "home" redirect_to root_path + elsif @comment.ultimate_parent.is_a?(AdminPost) + redirect_to unreviewed_admin_post_comments_path(@comment.ultimate_parent) else redirect_to unreviewed_work_comments_path(@comment.ultimate_parent) end @@ -447,7 +454,8 @@ def review end def review_all - unless @commentable && current_user_owns?(@commentable) + authorize @commentable, policy_class: CommentPolicy if logged_in_as_admin? + unless (@commentable && current_user_owns?(@commentable)) || (@commentable && logged_in_as_admin? && @commentable.is_a?(AdminPost)) flash[:error] = ts("What did you want to review comments on?") redirect_back_or_default(root_path) return diff --git a/app/helpers/comments_helper.rb b/app/helpers/comments_helper.rb index bdb68107905..869fefd86cf 100644 --- a/app/helpers/comments_helper.rb +++ b/app/helpers/comments_helper.rb @@ -186,6 +186,12 @@ def parent_disallows_comments?(comment) parent.disable_anon_comments? && !logged_in? end + def can_review_comment?(comment) + return false unless comment.unreviewed? + + is_author_of?(comment.ultimate_parent) || policy(comment).can_review_comment? + end + #### HELPERS FOR REPLYING TO COMMENTS ##### def add_cancel_comment_reply_link(comment) diff --git a/app/policies/comment_policy.rb b/app/policies/comment_policy.rb index 52745bc7ccc..b6d6e11087e 100644 --- a/app/policies/comment_policy.rb +++ b/app/policies/comment_policy.rb @@ -48,9 +48,19 @@ def can_mark_comment_spam? end end + def can_review_comment? + record.ultimate_parent.is_a?(AdminPost) && user&.is_a?(Admin) + end + + def can_review_all? + record.is_a?(AdminPost) && user&.is_a?(Admin) + end + alias destroy? can_destroy_comment? alias approve? can_mark_comment_spam? alias reject? can_mark_comment_spam? + alias review? can_review_comment? + alias review_all? can_review_all? def show_email? user_has_roles?(%w[policy_and_abuse support superadmin]) diff --git a/app/views/admin_posts/_admin_post_form.html.erb b/app/views/admin_posts/_admin_post_form.html.erb index a846d4d3460..eac6aca3895 100644 --- a/app/views/admin_posts/_admin_post_form.html.erb +++ b/app/views/admin_posts/_admin_post_form.html.erb @@ -67,6 +67,12 @@

    <%= t(".translated_post.footnote_tags") %>

    <% end %> +
    + <%= f.check_box :moderated_commenting_enabled %> +
    +
    + <%= f.label :moderated_commenting_enabled, t("comments.commentable.permissions.moderated_commenting.enable") %> +
    <%= t(".comment_permissions.label") %>
    diff --git a/app/views/admin_posts/show.html.erb b/app/views/admin_posts/show.html.erb index f49dd172e74..3518ecaa7bf 100755 --- a/app/views/admin_posts/show.html.erb +++ b/app/views/admin_posts/show.html.erb @@ -1,31 +1,36 @@
    -

    <%= link_to ts("AO3 News"), admin_posts_path %>

    +

    <%= link_to t(".page_heading"), admin_posts_path %>

    -