commit
6031e692f0
|
@ -109,3 +109,6 @@ bundler_stubs/*
|
|||
|
||||
vendor/bundle/*
|
||||
*.db
|
||||
|
||||
#ignore jetbrains ide file
|
||||
*.iml
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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});
|
||||
},
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
}
|
||||
|
||||
.keyboard-shortcuts-modal .modal-body {
|
||||
max-height: 520px;
|
||||
max-height: 560px;
|
||||
}
|
||||
|
||||
#keyboard-shortcuts-help {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 %>
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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+/}
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 }
|
||||
|
|
Loading…
Reference in New Issue