diff --git a/app/assets/stylesheets/components/_answerbox.scss b/app/assets/stylesheets/components/_answerbox.scss index 6ef19d72..74b33157 100644 --- a/app/assets/stylesheets/components/_answerbox.scss +++ b/app/assets/stylesheets/components/_answerbox.scss @@ -80,7 +80,7 @@ } body:not(.cap-web-share) { - .answerbox__action[name="ab-share"] { + [name="ab-share"] { display: none; } } diff --git a/app/helpers/social_helper.rb b/app/helpers/social_helper.rb new file mode 100644 index 00000000..00447583 --- /dev/null +++ b/app/helpers/social_helper.rb @@ -0,0 +1,4 @@ +module SocialHelper + include SocialHelper::TwitterMethods + include SocialHelper::TumblrMethods +end \ No newline at end of file diff --git a/app/helpers/social_helper/tumblr_methods.rb b/app/helpers/social_helper/tumblr_methods.rb new file mode 100644 index 00000000..f6e4710c --- /dev/null +++ b/app/helpers/social_helper/tumblr_methods.rb @@ -0,0 +1,35 @@ +require 'cgi' + +module SocialHelper::TumblrMethods + def tumblr_title(answer) + asker = if answer.question.author_is_anonymous? + APP_CONFIG['anonymous_name'] + else + answer.question.user.profile.safe_name + end + + "#{asker} asked: #{answer.question.content}" + end + + def tumblr_body(answer) + answer_url = show_user_answer_url( + id: answer.id, + username: answer.user.screen_name, + host: APP_CONFIG['hostname'], + protocol: (APP_CONFIG['https'] ? :https : :http) + ) + + "#{answer.content}\n\n[Smile or comment on the answer here](#{answer_url})" + end + + def tumblr_share_url(answer) + answer_url = show_user_answer_url( + id: answer.id, + username: answer.user.screen_name, + host: APP_CONFIG['hostname'], + protocol: (APP_CONFIG['https'] ? :https : :http) + ) + + "https://www.tumblr.com/widgets/share/tool?shareSource=legacy&posttype=text&title=#{CGI.escape(tumblr_title(answer))}&url=#{CGI.escape(answer_url)}&caption=&content=#{CGI.escape(tumblr_body(answer))}" + end +end \ No newline at end of file diff --git a/app/helpers/social_helper/twitter_methods.rb b/app/helpers/social_helper/twitter_methods.rb new file mode 100644 index 00000000..03a473e8 --- /dev/null +++ b/app/helpers/social_helper/twitter_methods.rb @@ -0,0 +1,37 @@ +require 'cgi' + +module SocialHelper::TwitterMethods + include MarkdownHelper + + def prepare_tweet(answer) + question_content = twitter_markdown answer.question.content.gsub(/\@(\w+)/, '\1') + original_question_length = question_content.length + answer_content = twitter_markdown answer.content + original_answer_length = answer_content.length + answer_url = show_user_answer_url( + id: answer.id, + username: answer.user.screen_name, + host: APP_CONFIG['hostname'], + protocol: (APP_CONFIG['https'] ? :https : :http) + ) + + parsed_tweet = { :valid => false } + tweet_text = "" + + until parsed_tweet[:valid] + tweet_text = "#{question_content[0..122]}#{'…' if original_question_length > [123, question_content.length].min}" \ + " — #{answer_content[0..123]}#{'…' if original_answer_length > [124, answer_content.length].min} #{answer_url}" + + parsed_tweet = Twitter::TwitterText::Validation::parse_tweet(tweet_text) + + question_content = question_content[0..-2] + answer_content = answer_content[0..-2] + end + + tweet_text + end + + def twitter_share_url(answer) + "https://twitter.com/intent/tweet?text=#{CGI.escape(prepare_tweet(answer))}" + end +end \ No newline at end of file diff --git a/app/javascript/retrospring/features/answerbox/index.ts b/app/javascript/retrospring/features/answerbox/index.ts index 9f836842..846a1ca8 100644 --- a/app/javascript/retrospring/features/answerbox/index.ts +++ b/app/javascript/retrospring/features/answerbox/index.ts @@ -1,17 +1,10 @@ -import registerEvents from 'utilities/registerEvents'; -import { createShareEvent } from './share'; +import { on } from 'retrospring/utilities/on'; +import { shareEventHandler } from './share'; export default (): void => { if ('share' in navigator) { document.body.classList.add('cap-web-share'); - const entries: NodeList = document.querySelectorAll('.answerbox:not(.js-initialized)'); - entries.forEach((element: HTMLElement) => { - registerEvents([ - { type: 'click', target: element.querySelector('[name=ab-share]'), handler: createShareEvent(element) } - ]); - - element.classList.add('js-initialized'); - }); + on('click', '[name=ab-share]', shareEventHandler); } } diff --git a/app/javascript/retrospring/features/answerbox/share.ts b/app/javascript/retrospring/features/answerbox/share.ts index 34c2ba54..9018550e 100644 --- a/app/javascript/retrospring/features/answerbox/share.ts +++ b/app/javascript/retrospring/features/answerbox/share.ts @@ -1,11 +1,12 @@ import noop from 'utilities/noop'; -export function createShareEvent(answerbox: HTMLElement): () => void { - return function (): void { - navigator.share({ - url: answerbox.querySelector('.answerbox__answer-date > a, a.answerbox__permalink').href - }) +export function shareEventHandler(event: Event): void { + event.preventDefault(); + const answerbox = (event.target as HTMLElement).closest('.answerbox'); + + navigator.share({ + url: answerbox.querySelector('.answerbox__answer-date > a, a.answerbox__permalink').href + }) .then(noop) .catch(noop) - } } diff --git a/app/models/services/tumblr.rb b/app/models/services/tumblr.rb index 8af15e1d..e99d8d56 100644 --- a/app/models/services/tumblr.rb +++ b/app/models/services/tumblr.rb @@ -1,6 +1,5 @@ class Services::Tumblr < Service - include Rails.application.routes.url_helpers - include MarkdownHelper + include SocialHelper::TumblrMethods def provider "tumblr" @@ -23,21 +22,10 @@ class Services::Tumblr < Service end def create_post(answer) - answer_url = show_user_answer_url( - id: answer.id, - username: answer.user.screen_name, - host: APP_CONFIG['hostname'], - protocol: (APP_CONFIG['https'] ? :https : :http) - ) - asker = if answer.question.author_is_anonymous? - APP_CONFIG['anonymous_name'] - else - answer.question.user.profile.display_name.blank? ? answer.question.user.screen_name : answer.question.user.profile.display_name - end client.text( self.uid, - title: "#{asker} asked: #{answer.question.content}", - body: "#{answer.content}\n\n[Smile or comment on the answer here](#{answer_url})", + title: tumblr_title(answer), + body: tumblr_body(answer), format: 'markdown', tweet: 'off' ) diff --git a/app/models/services/twitter.rb b/app/models/services/twitter.rb index 0e090bcd..53ae7b2a 100644 --- a/app/models/services/twitter.rb +++ b/app/models/services/twitter.rb @@ -1,6 +1,6 @@ class Services::Twitter < Service include Rails.application.routes.url_helpers - include MarkdownHelper + include SocialHelper::TwitterMethods def provider "twitter" @@ -25,32 +25,4 @@ class Services::Twitter < Service def post_tweet(answer) client.update! prepare_tweet(answer) end - - def prepare_tweet(answer) - question_content = twitter_markdown answer.question.content.gsub(/\@(\w+)/, '\1') - original_question_length = question_content.length - answer_content = twitter_markdown answer.content - original_answer_length = answer_content.length - answer_url = show_user_answer_url( - id: answer.id, - username: answer.user.screen_name, - host: APP_CONFIG['hostname'], - protocol: (APP_CONFIG['https'] ? :https : :http) - ) - - parsed_tweet = { :valid => false } - tweet_text = "" - - until parsed_tweet[:valid] - tweet_text = "#{question_content[0..122]}#{'…' if original_question_length > [123, question_content.length].min}" \ - " — #{answer_content[0..123]}#{'…' if original_answer_length > [124, answer_content.length].min} #{answer_url}" - - parsed_tweet = Twitter::TwitterText::Validation::parse_tweet(tweet_text) - - question_content = question_content[0..-2] - answer_content = answer_content[0..-2] - end - - tweet_text - end end diff --git a/app/views/answerbox/_actions.haml b/app/views/answerbox/_actions.haml index 3c8eadd3..ed9b053f 100644 --- a/app/views/answerbox/_actions.haml +++ b/app/views/answerbox/_actions.haml @@ -16,8 +16,18 @@ %button.btn.btn-link.answerbox__action{ type: :button, name: 'ab-comments', data: { a_id: a.id, state: :hidden } } %i.fa.fa-fw.fa-comments %span{ id: "ab-comment-count-#{a.id}" }= a.comment_count -%button.btn.btn-link.answerbox__action{ type: :button, name: 'ab-share' } - %i.fa.fa-fw.fa-share-alt{ title: 'Share' } +.btn-group + %button.btn.btn-link.answerbox__action{ data: { toggle: :dropdown }, aria: { expanded: false } } + %i.fa.fa-fw.fa-share-alt{ title: 'Share' } + .dropdown-menu.dropdown-menu-right{ role: :menu } + %a.dropdown-item{ href: twitter_share_url(a), target: '_blank' } + %i.fa.fa-fw.fa-twitter + Share on Twitter + %a.dropdown-item{ href: tumblr_share_url(a), target: '_blank' } + %i.fa.fa-fw.fa-tumblr + Share on Tumblr + %a.dropdown-item{ href: '#', name: 'ab-share' } + Share on other apps... - if user_signed_in? .btn-group %button.btn.btn-default.btn-sm.dropdown-toggle{ data: { toggle: :dropdown }, aria: { expanded: false } } diff --git a/spec/helpers/social_helper/tumblr_methods_spec.rb b/spec/helpers/social_helper/tumblr_methods_spec.rb new file mode 100644 index 00000000..da353770 --- /dev/null +++ b/spec/helpers/social_helper/tumblr_methods_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe SocialHelper::TumblrMethods, :type => :helper do + let(:user) { FactoryBot.create(:user) } + let(:answer) { FactoryBot.create(:answer, user: user, + content: 'aaaa', + question_content: 'q') } + + describe '#tumblr_title' do + context 'Asker is anonymous' do + subject { tumblr_title(answer) } + + it 'should return a proper title' do + expect(subject).to eq('Anonymous asked: q') + end + end + + context 'Asker is known' do + before do + @user = FactoryBot.create(:user) + answer.question.user = @user + answer.question.author_is_anonymous = false + end + + subject { tumblr_title(answer) } + + it 'should return a proper title' do + expect(subject).to eq("#{answer.question.user.profile.display_name} asked: q") + end + end + end + + describe '#tumblr_body' do + subject { tumblr_body(answer) } + + it 'should return a proper body' do + expect(subject).to eq("aaaa\n\n[Smile or comment on the answer here](https://justask.rrerr.net/#{answer.user.screen_name}/a/#{answer.id})") + end + end + + describe '#tumblr_share_url' do + subject { tumblr_share_url(answer) } + + it 'should return a proper share link' do + expect(subject).to eq("https://www.tumblr.com/widgets/share/tool?shareSource=legacy&posttype=text&title=#{CGI.escape(tumblr_title(answer))}&url=#{CGI.escape("https://justask.rrerr.net/#{answer.user.screen_name}/a/#{answer.id}")}&caption=&content=#{CGI.escape(tumblr_body(answer))}") + end + end +end \ No newline at end of file diff --git a/spec/helpers/social_helper/twitter_methods_spec.rb b/spec/helpers/social_helper/twitter_methods_spec.rb new file mode 100644 index 00000000..4c334fc6 --- /dev/null +++ b/spec/helpers/social_helper/twitter_methods_spec.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe SocialHelper::TwitterMethods, :type => :helper do + let(:user) { FactoryBot.create(:user) } + let(:answer) { FactoryBot.create(:answer, user: user, + content: 'a' * 255, + question_content: 'q' * 255) } + + describe '#prepare_tweet' do + context 'when the question and answer need to be shortened' do + subject { prepare_tweet(answer) } + + it 'should return a properly formatted tweet' do + expect(subject).to eq("#{'q' * 123}… — #{'a' * 124}… https://justask.rrerr.net/#{user.screen_name}/a/#{answer.id}") + end + end + + context 'when the question and answer are short' do + before do + answer.question.content = 'Why are raccoons so good?' + answer.question.save! + answer.content = 'Because they are good cunes.' + answer.save! + end + + subject { prepare_tweet(answer) } + + it 'should return a properly formatted tweet' do + expect(subject).to eq("#{answer.question.content} — #{answer.content} https://justask.rrerr.net/#{user.screen_name}/a/#{answer.id}") + end + end + end + + describe '#twitter_share_url' do + subject { twitter_share_url(answer) } + + it 'should return a proper share link' do + expect(subject).to eq("https://twitter.com/intent/tweet?text=#{CGI.escape(prepare_tweet(answer))}") + end + end +end \ No newline at end of file