diff --git a/app/controllers/ajax/answer_controller.rb b/app/controllers/ajax/answer_controller.rb index 859ae794..e038ae69 100644 --- a/app/controllers/ajax/answer_controller.rb +++ b/app/controllers/ajax/answer_controller.rb @@ -5,6 +5,7 @@ require "cgi" class Ajax::AnswerController < AjaxController include SocialHelper::TwitterMethods include SocialHelper::TumblrMethods + include SocialHelper::TelegramMethods def create params.require :id @@ -41,13 +42,7 @@ class Ajax::AnswerController < AjaxController @response[:message] = t(".success") @response[:success] = true - if current_user.sharing_enabled - @response[:sharing] = { - twitter: twitter_share_url(answer), - tumblr: tumblr_share_url(answer), - custom: CGI.escape(prepare_tweet(answer)) - } - end + @response[:sharing] = sharing_hash(answer) if current_user.sharing_enabled return if inbox @@ -74,4 +69,13 @@ class Ajax::AnswerController < AjaxController @response[:message] = t(".success") @response[:success] = true end + + private + + def sharing_hash(answer) = { + twitter: twitter_share_url(answer), + tumblr: tumblr_share_url(answer), + telegram: telegram_share_url(answer), + custom: CGI.escape(prepare_tweet(answer)), + } end diff --git a/app/helpers/social_helper.rb b/app/helpers/social_helper.rb index 00447583..e1ffe35f 100644 --- a/app/helpers/social_helper.rb +++ b/app/helpers/social_helper.rb @@ -1,4 +1,7 @@ +# frozen_string_literal: true + module SocialHelper include SocialHelper::TwitterMethods include SocialHelper::TumblrMethods -end \ No newline at end of file + include SocialHelper::TelegramMethods +end diff --git a/app/helpers/social_helper/telegram_methods.rb b/app/helpers/social_helper/telegram_methods.rb new file mode 100644 index 00000000..ec13f9ea --- /dev/null +++ b/app/helpers/social_helper/telegram_methods.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require "cgi" + +module SocialHelper::TelegramMethods + include MarkdownHelper + + def telegram_text(answer) + # using twitter_markdown here as it removes all formatting + "#{twitter_markdown answer.question.content}\n———\n#{twitter_markdown answer.content}" + end + + def telegram_share_url(answer) + url = answer_url( + id: answer.id, + username: answer.user.screen_name, + host: APP_CONFIG["hostname"], + protocol: (APP_CONFIG["https"] ? :https : :http) + ) + + %(https://t.me/share/url?url=#{CGI.escape(url)}&text=#{CGI.escape(telegram_text(answer))}) + end +end diff --git a/app/javascript/retrospring/controllers/inbox_sharing_controller.ts b/app/javascript/retrospring/controllers/inbox_sharing_controller.ts index 48f0a905..6d0218b1 100644 --- a/app/javascript/retrospring/controllers/inbox_sharing_controller.ts +++ b/app/javascript/retrospring/controllers/inbox_sharing_controller.ts @@ -1,10 +1,11 @@ import { Controller } from '@hotwired/stimulus'; export default class extends Controller { - static targets = ['twitter', 'tumblr', 'custom']; + static targets = ['twitter', 'tumblr', 'telegram', 'custom']; declare readonly twitterTarget: HTMLAnchorElement; declare readonly tumblrTarget: HTMLAnchorElement; + declare readonly telegramTarget: HTMLAnchorElement; declare readonly customTarget: HTMLAnchorElement; declare readonly hasCustomTarget: boolean; @@ -20,6 +21,7 @@ export default class extends Controller { if (this.autoCloseValue) { this.twitterTarget.addEventListener('click', () => this.close()); this.tumblrTarget.addEventListener('click', () => this.close()); + this.telegramTarget.addEventListener('click', () => this.close()); if (this.hasCustomTarget) { this.customTarget.addEventListener('click', () => this.close()); @@ -36,6 +38,7 @@ export default class extends Controller { this.twitterTarget.href = this.configValue['twitter']; this.tumblrTarget.href = this.configValue['tumblr']; + this.telegramTarget.href = this.configValue['telegram']; if (this.hasCustomTarget) { this.customTarget.href = `${this.customTarget.href}${this.configValue['custom']}`; diff --git a/app/views/actions/_share.html.haml b/app/views/actions/_share.html.haml index 85dbdd4e..65daacc2 100644 --- a/app/views/actions/_share.html.haml +++ b/app/views/actions/_share.html.haml @@ -5,5 +5,8 @@ %a.dropdown-item{ href: tumblr_share_url(answer), target: "_blank" } %i.fa.fa-fw.fa-tumblr = t(".tumblr") + %a.dropdown-item{ href: telegram_share_url(answer), target: "_blank" } + %i.fa.fa-fw.fa-telegram + = t(".telegram") %a.dropdown-item{ href: "#", name: "ab-share" } = t(".other") diff --git a/app/views/inbox/_entry.html.haml b/app/views/inbox/_entry.html.haml index 769d4f10..eb92ed7d 100644 --- a/app/views/inbox/_entry.html.haml +++ b/app/views/inbox/_entry.html.haml @@ -49,6 +49,9 @@ %a.btn.btn-primary{ href: "#", data: { inbox_sharing_target: "tumblr" }, target: "_blank" } %i.fab.fa-tumblr.fa-fw Tumblr + %a.btn.btn-primary{ href: "#", data: { inbox_sharing_target: "telegram" }, target: "_blank" } + %i.fab.fa-telegram.fa-fw + Telegram - if current_user.sharing_custom_url.present? %a.btn.btn-primary{ href: current_user.sharing_custom_url, data: { inbox_sharing_target: "custom" }, target: "_blank" } = current_user.display_sharing_custom_url diff --git a/config/locales/views.en.yml b/config/locales/views.en.yml index d16c9ce5..ba7a2d65 100644 --- a/config/locales/views.en.yml +++ b/config/locales/views.en.yml @@ -81,6 +81,7 @@ en: share: twitter: "Share on Twitter" tumblr: "Share on Tumblr" + telegram: "Share on Telegram" other: "Share on other apps..." admin: announcement: diff --git a/spec/controllers/ajax/answer_controller_spec.rb b/spec/controllers/ajax/answer_controller_spec.rb index 93374bb5..18c65354 100644 --- a/spec/controllers/ajax/answer_controller_spec.rb +++ b/spec/controllers/ajax/answer_controller_spec.rb @@ -13,7 +13,7 @@ describe Ajax::AnswerController, :ajax_controller, type: :controller do id:, answer:, share: shared_services&.to_json, - inbox: + inbox:, }.compact end @@ -42,7 +42,7 @@ describe Ajax::AnswerController, :ajax_controller, type: :controller do "success" => false, # caught by rescue_from, so status is not peter_dinklage "status" => "parameter_error", - "message" => anything + "message" => anything, } end @@ -70,13 +70,33 @@ describe Ajax::AnswerController, :ajax_controller, type: :controller do { "success" => true, "status" => "okay", - "message" => anything + "message" => anything, } end include_examples "creates the answer" it_behaves_like "fails when answer content is empty" + + context "when the user has sharing enabled" do + before do + user.sharing_enabled = true + user.save + end + + let(:expected_response) do + super().merge( + "sharing" => { + "twitter" => a_string_matching("https://twitter.com/"), + "tumblr" => a_string_matching("https://www.tumblr.com/"), + "telegram" => a_string_matching("https://t.me/"), + "custom" => a_string_matching(/Werfen\+Sie\+nicht\+l%C3%A4nger\+das\+Fenster\+zum\+Geld\+hinaus%21/), + } + ) + end + + include_examples "creates the answer" + end end context "when the inbox entry does not belong to the user" do @@ -85,7 +105,7 @@ describe Ajax::AnswerController, :ajax_controller, type: :controller do { "success" => false, "status" => "fail", - "message" => anything + "message" => anything, } end @@ -100,7 +120,7 @@ describe Ajax::AnswerController, :ajax_controller, type: :controller do { "success" => true, "status" => "okay", - "message" => anything + "message" => anything, } end @@ -129,13 +149,33 @@ describe Ajax::AnswerController, :ajax_controller, type: :controller do "success" => true, "status" => "okay", "message" => anything, - "render" => anything + "render" => anything, } end include_examples "creates the answer" it_behaves_like "fails when answer content is empty" + + context "when the user has sharing enabled" do + before do + user.sharing_enabled = true + user.save + end + + let(:expected_response) do + super().merge( + "sharing" => { + "twitter" => a_string_matching("https://twitter.com/"), + "tumblr" => a_string_matching("https://www.tumblr.com/"), + "telegram" => a_string_matching("https://t.me/"), + "custom" => a_string_matching(/Werfen\+Sie\+nicht\+l%C3%A4nger\+das\+Fenster\+zum\+Geld\+hinaus%21/), + } + ) + end + + include_examples "creates the answer" + end end context "when question asker does not allow strangers to answer (i.e. question was not in inbox)" do @@ -144,7 +184,7 @@ describe Ajax::AnswerController, :ajax_controller, type: :controller do { "success" => false, "status" => "privacy_stronk", - "message" => anything + "message" => anything, } end @@ -160,7 +200,7 @@ describe Ajax::AnswerController, :ajax_controller, type: :controller do { "success" => false, "status" => "answering_other_blocked_self", - "message" => I18n.t("errors.answering_other_blocked_self") + "message" => I18n.t("errors.answering_other_blocked_self"), } end @@ -176,7 +216,7 @@ describe Ajax::AnswerController, :ajax_controller, type: :controller do { "success" => false, "status" => "answering_self_blocked_other", - "message" => I18n.t("errors.answering_self_blocked_other") + "message" => I18n.t("errors.answering_self_blocked_other"), } end @@ -196,7 +236,7 @@ describe Ajax::AnswerController, :ajax_controller, type: :controller do "success" => false, # caught by rescue_from, so status is not peter_dinklage "status" => "parameter_error", - "message" => anything + "message" => anything, } end @@ -214,7 +254,7 @@ describe Ajax::AnswerController, :ajax_controller, type: :controller do { "success" => false, "status" => "err", - "message" => anything + "message" => anything, } end @@ -230,7 +270,7 @@ describe Ajax::AnswerController, :ajax_controller, type: :controller do let(:params) do { - answer: answer_id + answer: answer_id, } end @@ -242,7 +282,7 @@ describe Ajax::AnswerController, :ajax_controller, type: :controller do { "success" => true, "status" => "okay", - "message" => anything + "message" => anything, } end @@ -259,7 +299,7 @@ describe Ajax::AnswerController, :ajax_controller, type: :controller do { "success" => false, "status" => "nopriv", - "message" => anything + "message" => anything, } end @@ -311,7 +351,7 @@ describe Ajax::AnswerController, :ajax_controller, type: :controller do { "success" => false, "status" => anything, - "message" => anything + "message" => anything, } end @@ -324,7 +364,7 @@ describe Ajax::AnswerController, :ajax_controller, type: :controller do { "success" => false, "status" => "nopriv", - "message" => anything + "message" => anything, } end diff --git a/spec/helpers/social_helper/telegram_methods_spec.rb b/spec/helpers/social_helper/telegram_methods_spec.rb new file mode 100644 index 00000000..5e180373 --- /dev/null +++ b/spec/helpers/social_helper/telegram_methods_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require "rails_helper" + +describe SocialHelper::TelegramMethods, type: :helper do + let(:user) { FactoryBot.create(:user) } + let(:answer) do + FactoryBot.create( + :answer, + user:, + content: "this is an answer\nwith multiple lines\nand **FORMATTING**", + question_content: "this is a question .... or is it?" + ) + end + + before do + stub_const("APP_CONFIG", { + "hostname" => "example.com", + "https" => true, + "items_per_page" => 5, + }) + end + + describe "#telegram_text" do + subject { telegram_text(answer) } + + it "returns a proper text for sharing" do + expect(subject).to eq(<<~TEXT.strip) + this is a question .... or is it? + ——— + this is an answer + with multiple lines + and FORMATTING + TEXT + end + end + + describe "#telegram_share_url" do + subject { telegram_share_url(answer) } + + it "returns a proper share link" do + expect(subject).to eq(<<~URL.strip) + https://t.me/share/url?url=https%3A%2F%2Fexample.com%2F%40#{answer.user.screen_name}%2Fa%2F#{answer.id}&text=this+is+a+question+....+or+is+it%3F%0A%E2%80%94%E2%80%94%E2%80%94%0Athis+is+an+answer%0Awith+multiple+lines%0Aand+FORMATTING + URL + end + end +end diff --git a/spec/views/actions/_share.html.haml_spec.rb b/spec/views/actions/_share.html.haml_spec.rb new file mode 100644 index 00000000..16153d1c --- /dev/null +++ b/spec/views/actions/_share.html.haml_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require "rails_helper" + +describe "actions/_share.html.haml", type: :view do + let(:answer) { FactoryBot.create(:answer, user: FactoryBot.create(:user)) } + + subject(:rendered) do + render partial: "actions/share", locals: { + answer:, + } + end + + it "has a dropdown item to share to twitter" do + expect(rendered).to have_css(%(a.dropdown-item[href^="https://twitter.com/"][target="_blank"])) + end + + it "has a dropdown item to share to tumblr" do + expect(rendered).to have_css(%(a.dropdown-item[href^="https://www.tumblr.com/"][target="_blank"])) + end + + it "has a dropdown item to share to telegram" do + expect(rendered).to have_css(%(a.dropdown-item[href^="https://t.me/"][target="_blank"])) + end + + it "has a dropdown item to share to anywhere else" do + expect(rendered).to have_css(%(a.dropdown-item[name="ab-share"])) + end +end diff --git a/spec/views/inbox/_entry.html.haml_spec.rb b/spec/views/inbox/_entry.html.haml_spec.rb index 72243982..a94c2c1c 100644 --- a/spec/views/inbox/_entry.html.haml_spec.rb +++ b/spec/views/inbox/_entry.html.haml_spec.rb @@ -82,6 +82,10 @@ describe "inbox/_entry.html.haml", type: :view do expect(rendered).to have_css(%(.inbox-entry__sharing.d-none)) end + it "has a link-button to share to telegram" do + expect(rendered).to have_css(%(.inbox-entry__sharing a.btn[data-inbox-sharing-target="telegram"])) + end + it "has a link-button to share to tumblr" do expect(rendered).to have_css(%(.inbox-entry__sharing a.btn[data-inbox-sharing-target="tumblr"])) end