Merge pull request #4366 from xfalcox/print

Print Support
This commit is contained in:
Sam 2016-10-11 11:47:20 +11:00 committed by GitHub
commit 6031e692f0
18 changed files with 122 additions and 10 deletions

3
.gitignore vendored
View File

@ -109,3 +109,6 @@ bundler_stubs/*
vendor/bundle/*
*.db
#ignore jetbrains ide file
*.iml

View File

@ -687,6 +687,12 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
this.send('togglePinnedForUser');
},
print() {
if (this.siteSettings.max_prints_per_hour_per_user > 0) {
window.open(this.get('model.printUrl'), '', 'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,width=600,height=315');
}
},
canMergeTopic: function() {
if (!this.get('model.details.can_move_posts')) return false;
return this.get('selectedPostsCount') > 0;

View File

@ -13,6 +13,8 @@ const bindings = {
'c': {handler: 'createTopic'},
'ctrl+f': {handler: 'showPageSearch', anonymous: true},
'command+f': {handler: 'showPageSearch', anonymous: true},
'ctrl+p': {handler: 'printTopic', anonymous: true},
'command+p': {handler: 'printTopic', anonymous: true},
'd': {postAction: 'deletePost'},
'e': {postAction: 'editPost'},
'end': {handler: 'goToLastPost', anonymous: true},
@ -151,6 +153,15 @@ export default {
});
},
printTopic(event) {
Ember.run(() => {
if ($('.container.posts').length) {
event.preventDefault(); // We need to stop printing the current page in Firefox
this.container.lookup('controller:topic').print();
}
});
},
createTopic() {
this.container.lookup('controller:composer').open({action: Composer.CREATE_TOPIC, draftKey: Composer.CREATE_TOPIC});
},

View File

@ -134,6 +134,11 @@ const Topic = RestModel.extend({
return this.get('url') + (user ? '?u=' + user.get('username_lower') : '');
}.property('url'),
@computed('url')
printUrl(url) {
return url + '/print';
},
url: function() {
let slug = this.get('slug') || '';
if (slug.trim().length === 0) {

View File

@ -56,6 +56,7 @@
<li>{{{i18n 'keyboard_shortcuts_help.actions.mark_regular'}}}</li>
<li>{{{i18n 'keyboard_shortcuts_help.actions.mark_tracking'}}}</li>
<li>{{{i18n 'keyboard_shortcuts_help.actions.mark_watching'}}}</li>
<li>{{{i18n 'keyboard_shortcuts_help.actions.print'}}}</li>
</ul>
</div>
</div>

View File

@ -11,7 +11,7 @@
}
.keyboard-shortcuts-modal .modal-body {
max-height: 520px;
max-height: 560px;
}
#keyboard-shortcuts-help {

View File

@ -53,7 +53,7 @@ class ApplicationController < ActionController::Base
end
def use_crawler_layout?
@use_crawler_layout ||= (has_escaped_fragment? || CrawlerDetection.crawler?(request.user_agent))
@use_crawler_layout ||= (has_escaped_fragment? || CrawlerDetection.crawler?(request.user_agent) || params.key?("print"))
end
def add_readonly_header

View File

@ -3,6 +3,7 @@ require_dependency 'promotion'
require_dependency 'url_helper'
require_dependency 'topics_bulk_action'
require_dependency 'discourse_event'
require_dependency 'rate_limiter'
class TopicsController < ApplicationController
before_filter :ensure_logged_in, only: [:timings,
@ -58,6 +59,7 @@ class TopicsController < ApplicationController
username_filters = opts[:username_filters]
opts[:slow_platform] = true if slow_platform?
opts[:print] = true if params[:print].present?
opts[:username_filters] = username_filters.split(',') if username_filters.is_a?(String)
# Special case: a slug with a number in front should look by slug first before looking
@ -67,6 +69,15 @@ class TopicsController < ApplicationController
return redirect_to_correct_topic(topic, opts[:post_number]) if topic && topic.visible
end
if opts[:print]
raise Discourse::InvalidAccess unless SiteSetting.max_prints_per_hour_per_user > 0
begin
RateLimiter.new(current_user, "print-topic-per-hour", SiteSetting.max_prints_per_hour_per_user, 1.hour).performed! unless @guardian.is_admin?
rescue RateLimiter::LimitExceeded
render_json_error(I18n.t("rate_limiter.slow_down"))
end
end
begin
@topic_view = TopicView.new(params[:id] || params[:topic_id], current_user, opts)
rescue Discourse::NotFound

View File

@ -43,6 +43,11 @@
.topic-list > div {
margin-bottom: 15px;
}
body img.emoji {
width: 20px;
height: 20px;
vertical-align: middle;
}
</style>
</head>
@ -71,4 +76,5 @@
<%= raw SiteCustomization.custom_body_tag(session[:preview_style]) %>
<%- end %>
</body>
<%= yield :after_body %>
</html>

View File

@ -66,3 +66,13 @@
<% end %>
<% content_for(:title) { "#{@topic_view.page_title}" } %>
<% if @topic_view.print %>
<% content_for :after_body do %>
<script>
document.addEventListener("DOMContentLoaded", function() {
window.print();
});
</script>
<% end %>
<% end %>

View File

@ -1502,6 +1502,10 @@ en:
title: 'Share'
help: 'share a link to this topic'
print:
title: 'Print'
help: 'Open a printer friendly version of this topic'
flag_topic:
title: 'Flag'
help: 'privately flag this topic for attention or send a private notification about it'
@ -2161,6 +2165,7 @@ en:
mark_regular: '<b>m</b>, <b>r</b> Regular (default) topic'
mark_tracking: '<b>m</b>, <b>t</b> Track topic'
mark_watching: '<b>m</b>, <b>w</b> Watch topic'
print: '<b>ctrl</b>+<b>p</b> Print topic'
badges:
earned_n_times:

View File

@ -1307,6 +1307,8 @@ en:
topic_page_title_includes_category: "Topic page title includes the category name."
max_prints_per_hour_per_user: "Maximum number of /print page impressions (set to 0 to disable)"
full_name_required: "Full name is a required field of a user's profile."
enable_names: "Show the user's full name on their profile, user card, and emails. Disable to hide full name everywhere."
display_name_on_posts: "Show a user's full name on their posts in addition to their @username."

View File

@ -554,6 +554,7 @@ Discourse::Application.routes.draw do
# Topic routes
get "t/id_for/:slug" => "topics#id_for_slug"
get "t/:slug/:topic_id/print" => "topics#show", format: :html, print: true, constraints: {topic_id: /\d+/}
get "t/:slug/:topic_id/wordpress" => "topics#wordpress", constraints: {topic_id: /\d+/}
get "t/:topic_id/wordpress" => "topics#wordpress", constraints: {topic_id: /\d+/}
get "t/:slug/:topic_id/moderator-liked" => "topics#moderator_liked", constraints: {topic_id: /\d+/}

View File

@ -915,6 +915,9 @@ rate_limits:
client: true
shadowed_by_global: true
default: 0
max_prints_per_hour_per_user:
default: 5
client: true
developer:
force_hostname:
@ -1226,6 +1229,7 @@ uncategorized:
topic_page_title_includes_category: true
user_preferences:
default_email_digest_frequency:
enum: 'DigestEmailSiteSetting'

View File

@ -5,13 +5,17 @@ require_dependency 'gaps'
class TopicView
attr_reader :topic, :posts, :guardian, :filtered_posts, :chunk_size
attr_reader :topic, :posts, :guardian, :filtered_posts, :chunk_size, :print
attr_accessor :draft, :draft_key, :draft_sequence, :user_custom_fields, :post_custom_fields
def self.slow_chunk_size
10
end
def self.print_chunk_size
1000
end
def self.chunk_size
20
end
@ -37,6 +41,7 @@ class TopicView
@user = user
@guardian = Guardian.new(@user)
@topic = find_topic(topic_id)
@print = options[:print].present?
check_and_raise_exceptions
options.each do |key, value|
@ -44,7 +49,11 @@ class TopicView
end
@page = 1 if (!@page || @page.zero?)
@chunk_size = options[:slow_platform] ? TopicView.slow_chunk_size : TopicView.chunk_size
@chunk_size = case
when options[:slow_platform] then TopicView.slow_chunk_size
when @print then TopicView.print_chunk_size
else TopicView.chunk_size
end
@limit ||= @chunk_size
setup_filtered_posts
@ -71,7 +80,7 @@ class TopicView
end
def canonical_path
path = @topic.relative_url
path = relative_url
path << if @post_number
page = ((@post_number.to_i - 1) / @limit) + 1
(page > 1) ? "?page=#{page}" : ""
@ -113,22 +122,22 @@ class TopicView
def prev_page_path
if prev_page > 1
"#{@topic.relative_url}?page=#{prev_page}"
"#{relative_url}?page=#{prev_page}"
else
@topic.relative_url
relative_url
end
end
def next_page_path
"#{@topic.relative_url}?page=#{next_page}"
"#{relative_url}?page=#{next_page}"
end
def absolute_url
"#{Discourse.base_url}#{@topic.relative_url}"
"#{Discourse.base_url}#{relative_url}"
end
def relative_url
@topic.relative_url
"#{@topic.relative_url}#{@print ? '/print' : ''}"
end
def page_title

View File

@ -35,6 +35,11 @@ describe TopicView do
tv = TopicView.new(topic.id, coding_horror, slow_platform: true)
expect(tv.chunk_size).to eq(TopicView.slow_chunk_size)
end
it "returns `print_chunk_size` when print param is true" do
tv = TopicView.new(topic.id, coding_horror, print: true)
expect(tv.chunk_size).to eq(TopicView.print_chunk_size)
end
end
context "with a few sample posts" do

View File

@ -84,6 +84,24 @@ describe TopicsController do
end
describe "print" do
render_views
context "when the SiteSetting is enabled" do
it "uses the application layout when there's no param" do
get :show, topic_id: topic.id, slug: topic.slug
expect(response).to render_template(layout: 'application')
assert_select "meta[name=fragment]", true, "it has the meta tag"
end
it "uses the crawler layout when there's an print param" do
get :show, topic_id: topic.id, slug: topic.slug, print: 'true'
expect(response).to render_template(layout: 'crawler')
assert_select "meta[name=fragment]", false, "it doesn't have the meta tag"
end
end
end
describe 'clear_notifications' do
it 'correctly clears notifications if specified via cookie' do
notification = Fabricate(:notification)

View File

@ -747,6 +747,21 @@ describe TopicsController do
expect(IncomingLink.count).to eq(1)
end
context 'print' do
it "doesn't renders the print view when disabled" do
SiteSetting.max_prints_per_hour_per_user = 0
get :show, topic_id: topic.id, slug: topic.slug, print: true
expect(response).to be_forbidden
end
it 'renders the print view when enabled' do
SiteSetting.max_prints_per_hour_per_user = 10
get :show, topic_id: topic.id, slug: topic.slug, print: true
expect(response).to be_successful
end
end
it 'records redirects' do
@request.env['HTTP_REFERER'] = 'http://twitter.com'
get :show, { id: topic.id }