mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
FEATURE: allows published pages to be public (#10053)
This commit is contained in:
parent
7d289a4f3e
commit
9da3a7f436
@ -35,6 +35,7 @@ export default Controller.extend(ModalFunctionality, StateHelpers, {
|
|||||||
this.state === States.existing
|
this.state === States.existing
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
|
|
||||||
showUnpublish: computed("state", function() {
|
showUnpublish: computed("state", function() {
|
||||||
return this.state === States.existing || this.state === States.unpublishing;
|
return this.state === States.existing || this.state === States.unpublishing;
|
||||||
}),
|
}),
|
||||||
@ -95,7 +96,7 @@ export default Controller.extend(ModalFunctionality, StateHelpers, {
|
|||||||
this.set("state", States.saving);
|
this.set("state", States.saving);
|
||||||
|
|
||||||
return this.publishedPage
|
return this.publishedPage
|
||||||
.update({ slug: this.publishedPage.slug })
|
.update(this.publishedPage.getProperties("slug", "public"))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.set("state", States.existing);
|
this.set("state", States.existing);
|
||||||
this.model.set("publishedPage", this.publishedPage);
|
this.model.set("publishedPage", this.publishedPage);
|
||||||
@ -110,11 +111,17 @@ export default Controller.extend(ModalFunctionality, StateHelpers, {
|
|||||||
startNew() {
|
startNew() {
|
||||||
this.setProperties({
|
this.setProperties({
|
||||||
state: States.new,
|
state: States.new,
|
||||||
publishedPage: this.store.createRecord("published_page", {
|
publishedPage: this.store.createRecord(
|
||||||
id: this.model.id,
|
"published_page",
|
||||||
slug: this.model.slug
|
this.model.getProperties("id", "slug", "public")
|
||||||
})
|
)
|
||||||
});
|
});
|
||||||
this.checkSlug();
|
this.checkSlug();
|
||||||
|
},
|
||||||
|
|
||||||
|
@action
|
||||||
|
onChangePublic(isPublic) {
|
||||||
|
this.publishedPage.set("public", isPublic);
|
||||||
|
this.publish();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -6,8 +6,29 @@
|
|||||||
<p class="publish-description">{{i18n "topic.publish_page.description"}}</p>
|
<p class="publish-description">{{i18n "topic.publish_page.description"}}</p>
|
||||||
|
|
||||||
<form>
|
<form>
|
||||||
<label>{{i18n "topic.publish_page.slug"}}</label>
|
<div class="controls">
|
||||||
{{text-field value=publishedPage.slug onChange=(action "checkSlug") onChangeImmediate=(action "startCheckSlug") disabled=existing class="publish-slug"}}
|
<label>{{i18n "topic.publish_page.slug"}}</label>
|
||||||
|
{{text-field
|
||||||
|
value=publishedPage.slug
|
||||||
|
onChange=(action "checkSlug")
|
||||||
|
onChangeImmediate=(action "startCheckSlug")
|
||||||
|
disabled=existing
|
||||||
|
class="publish-slug"
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="controls">
|
||||||
|
<label>{{i18n "topic.publish_page.public"}}</label>
|
||||||
|
|
||||||
|
<p class="description">
|
||||||
|
{{input
|
||||||
|
type="checkbox"
|
||||||
|
checked=publishedPage.public
|
||||||
|
click=(action "onChangePublic" value="target.checked")
|
||||||
|
}}
|
||||||
|
{{i18n "topic.publish_page.public_description"}}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div class="publish-url">
|
<div class="publish-url">
|
||||||
@ -40,14 +61,15 @@
|
|||||||
|
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
{{#if showUnpublish}}
|
{{#if showUnpublish}}
|
||||||
{{d-button icon="times" label="close" action=(action "closeModal")}}
|
|
||||||
|
|
||||||
{{d-button
|
{{d-button
|
||||||
label="topic.publish_page.unpublish"
|
label="topic.publish_page.unpublish"
|
||||||
icon="trash-alt"
|
icon="trash-alt"
|
||||||
class="btn-danger"
|
class="btn-danger"
|
||||||
isLoading=unpublishing
|
isLoading=unpublishing
|
||||||
action=(action "unpublish") }}
|
action=(action "unpublish")
|
||||||
|
}}
|
||||||
|
|
||||||
|
{{d-button class="close-publish-page" icon="times" label="close" action=(action "closeModal")}}
|
||||||
{{else if unpublished}}
|
{{else if unpublished}}
|
||||||
{{d-button label="topic.publish_page.publishing_settings" action=(action "startNew")}}
|
{{d-button label="topic.publish_page.publishing_settings" action=(action "startNew")}}
|
||||||
{{else}}
|
{{else}}
|
||||||
|
@ -90,6 +90,9 @@
|
|||||||
{{#if model.publishedPage}}
|
{{#if model.publishedPage}}
|
||||||
<div class="published-page-notice">
|
<div class="published-page-notice">
|
||||||
<div class="details">
|
<div class="details">
|
||||||
|
{{#if model.publishedPage.public}}
|
||||||
|
<span class="is-public">{{i18n "topic.publish_page.public"}}</span>
|
||||||
|
{{/if}}
|
||||||
{{i18n "topic.publish_page.topic_published"}}
|
{{i18n "topic.publish_page.topic_published"}}
|
||||||
<div>
|
<div>
|
||||||
<a href={{model.publishedPage.url}} target="_blank" rel="noopener noreferrer">{{model.publishedPage.url}}</a>
|
<a href={{model.publishedPage.url}} target="_blank" rel="noopener noreferrer">{{model.publishedPage.url}}</a>
|
||||||
|
@ -689,24 +689,45 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.publish-page-modal .modal-body {
|
.publish-page-modal {
|
||||||
p.publish-description {
|
.modal-body {
|
||||||
margin-top: 0;
|
p.publish-description {
|
||||||
}
|
margin-top: 0;
|
||||||
input.publish-slug {
|
}
|
||||||
width: 100%;
|
input.publish-slug {
|
||||||
}
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.publish-url {
|
.publish-url {
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
.example-url,
|
.example-url,
|
||||||
.invalid-slug {
|
.invalid-slug {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.publish-slug:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
|
||||||
|
.description {
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.publish-slug:disabled {
|
.modal-footer {
|
||||||
cursor: not-allowed;
|
display: flex;
|
||||||
|
|
||||||
|
.close-publish-page {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -305,4 +305,13 @@ a.topic-featured-link {
|
|||||||
2}
|
2}
|
||||||
);
|
);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
|
.is-public {
|
||||||
|
padding: 0.25em 0.5em;
|
||||||
|
font-size: $font-down-2;
|
||||||
|
background: $tertiary;
|
||||||
|
color: $secondary;
|
||||||
|
border-radius: 3px;
|
||||||
|
text-transform: lowercase;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ class PublishedPagesController < ApplicationController
|
|||||||
skip_before_action :preload_json
|
skip_before_action :preload_json
|
||||||
skip_before_action :check_xhr, :verify_authenticity_token, only: [:show]
|
skip_before_action :check_xhr, :verify_authenticity_token, only: [:show]
|
||||||
before_action :ensure_publish_enabled
|
before_action :ensure_publish_enabled
|
||||||
|
before_action :redirect_to_login_if_required, except: [:show]
|
||||||
|
|
||||||
def show
|
def show
|
||||||
params.require(:slug)
|
params.require(:slug)
|
||||||
@ -12,7 +13,22 @@ class PublishedPagesController < ApplicationController
|
|||||||
pp = PublishedPage.find_by(slug: params[:slug])
|
pp = PublishedPage.find_by(slug: params[:slug])
|
||||||
raise Discourse::NotFound unless pp
|
raise Discourse::NotFound unless pp
|
||||||
|
|
||||||
guardian.ensure_can_see!(pp.topic)
|
return if enforce_login_required!
|
||||||
|
|
||||||
|
if !pp.public
|
||||||
|
begin
|
||||||
|
guardian.ensure_can_see!(pp.topic)
|
||||||
|
rescue Discourse::InvalidAccess => e
|
||||||
|
return rescue_discourse_actions(
|
||||||
|
:invalid_access,
|
||||||
|
403,
|
||||||
|
include_ember: false,
|
||||||
|
custom_message: e.custom_message,
|
||||||
|
group: e.group
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@topic = pp.topic
|
@topic = pp.topic
|
||||||
@canonical_url = @topic.url
|
@canonical_url = @topic.url
|
||||||
|
|
||||||
@ -37,7 +53,15 @@ class PublishedPagesController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def upsert
|
def upsert
|
||||||
result, pp = PublishedPage.publish!(current_user, fetch_topic, params[:published_page][:slug].strip)
|
pp_params = params.require(:published_page)
|
||||||
|
|
||||||
|
result, pp = PublishedPage.publish!(
|
||||||
|
current_user,
|
||||||
|
fetch_topic,
|
||||||
|
pp_params[:slug].strip,
|
||||||
|
pp_params.permit(:public)
|
||||||
|
)
|
||||||
|
|
||||||
json_result(pp, serializer: PublishedPageSerializer) { result }
|
json_result(pp, serializer: PublishedPageSerializer) { result }
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -68,4 +92,13 @@ private
|
|||||||
raise Discourse::NotFound unless SiteSetting.enable_page_publishing?
|
raise Discourse::NotFound unless SiteSetting.enable_page_publishing?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def enforce_login_required!
|
||||||
|
if SiteSetting.login_required? &&
|
||||||
|
!current_user &&
|
||||||
|
!SiteSetting.show_published_pages_login_required? &&
|
||||||
|
redirect_to_login
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
@ -23,12 +23,13 @@ class PublishedPage < ActiveRecord::Base
|
|||||||
"#{Discourse.base_url}#{path}"
|
"#{Discourse.base_url}#{path}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.publish!(publisher, topic, slug)
|
def self.publish!(publisher, topic, slug, options = {})
|
||||||
pp = nil
|
pp = nil
|
||||||
|
|
||||||
transaction do
|
transaction do
|
||||||
pp = find_or_initialize_by(topic: topic)
|
pp = find_or_initialize_by(topic: topic)
|
||||||
pp.slug = slug.strip
|
pp.slug = slug.strip
|
||||||
|
pp.public = options[:public] || false
|
||||||
|
|
||||||
if pp.save
|
if pp.save
|
||||||
StaffActionLogger.new(publisher).log_published_page(topic.id, slug)
|
StaffActionLogger.new(publisher).log_published_page(topic.id, slug)
|
||||||
@ -56,6 +57,7 @@ end
|
|||||||
# slug :string not null
|
# slug :string not null
|
||||||
# created_at :datetime not null
|
# created_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# updated_at :datetime not null
|
||||||
|
# public :boolean default(FALSE), not null
|
||||||
#
|
#
|
||||||
# Indexes
|
# Indexes
|
||||||
#
|
#
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class PublishedPageSerializer < ApplicationSerializer
|
class PublishedPageSerializer < ApplicationSerializer
|
||||||
attributes :id, :slug
|
attributes :id, :slug, :public
|
||||||
|
|
||||||
def id
|
def id
|
||||||
object.topic_id
|
object.topic_id
|
||||||
|
@ -2513,11 +2513,14 @@ en:
|
|||||||
publish: "Publish"
|
publish: "Publish"
|
||||||
description: "When a topic is published as a page, its URL can be shared and it will displayed with custom styling."
|
description: "When a topic is published as a page, its URL can be shared and it will displayed with custom styling."
|
||||||
slug: "Slug"
|
slug: "Slug"
|
||||||
|
public: "Public"
|
||||||
|
public_description: "People can see the page even if the associated topic is private."
|
||||||
publish_url: "Your page has been published at:"
|
publish_url: "Your page has been published at:"
|
||||||
topic_published: "Your topic has been published at:"
|
topic_published: "Your topic has been published at:"
|
||||||
preview_url: "Your page will be published at:"
|
preview_url: "Your page will be published at:"
|
||||||
invalid_slug: "Sorry, you can't publish this page."
|
invalid_slug: "Sorry, you can't publish this page."
|
||||||
unpublish: "Unpublish"
|
unpublish: "Unpublish"
|
||||||
|
update: "Update"
|
||||||
unpublished: "Your page has been unpublished and is no longer accessible."
|
unpublished: "Your page has been unpublished and is no longer accessible."
|
||||||
publishing_settings: "Publishing Settings"
|
publishing_settings: "Publishing Settings"
|
||||||
|
|
||||||
|
@ -2131,6 +2131,7 @@ en:
|
|||||||
returning_user_notice_tl: "Minimum trust level required to see returning user post notices."
|
returning_user_notice_tl: "Minimum trust level required to see returning user post notices."
|
||||||
returning_users_days: "How many days should pass before a user is considered to be returning."
|
returning_users_days: "How many days should pass before a user is considered to be returning."
|
||||||
enable_page_publishing: "Allow staff members to publish topics to new URLs with their own styling."
|
enable_page_publishing: "Allow staff members to publish topics to new URLs with their own styling."
|
||||||
|
show_published_pages_login_required: "Anonymous users can see published pages, even when login is required."
|
||||||
|
|
||||||
default_email_digest_frequency: "How often users receive summary emails by default."
|
default_email_digest_frequency: "How often users receive summary emails by default."
|
||||||
default_include_tl0_in_digests: "Include posts from new users in summary emails by default. Users can change this in their preferences."
|
default_include_tl0_in_digests: "Include posts from new users in summary emails by default. Users can change this in their preferences."
|
||||||
|
@ -947,6 +947,8 @@ posting:
|
|||||||
max: 36500
|
max: 36500
|
||||||
enable_page_publishing:
|
enable_page_publishing:
|
||||||
default: false
|
default: false
|
||||||
|
show_published_pages_login_required:
|
||||||
|
default: false
|
||||||
|
|
||||||
email:
|
email:
|
||||||
email_time_window_mins:
|
email_time_window_mins:
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddPublicFieldToPublishedPages < ActiveRecord::Migration[6.0]
|
||||||
|
def change
|
||||||
|
add_column :published_pages, :public, :boolean, null: false, default: false
|
||||||
|
end
|
||||||
|
end
|
@ -2,5 +2,6 @@
|
|||||||
|
|
||||||
Fabricator(:published_page) do
|
Fabricator(:published_page) do
|
||||||
topic
|
topic
|
||||||
slug "published-page-test"
|
slug "published-page-test-#{SecureRandom.hex}"
|
||||||
|
public false
|
||||||
end
|
end
|
||||||
|
@ -59,6 +59,15 @@ RSpec.describe PublishedPagesController do
|
|||||||
expect(response.status).to eq(403)
|
expect(response.status).to eq(403)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "published page is public" do
|
||||||
|
fab!(:public_published_page) { Fabricate(:published_page, public: true) }
|
||||||
|
|
||||||
|
it "returns 200 for a topic you can't see" do
|
||||||
|
get public_published_page.path
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context "as an admin" do
|
context "as an admin" do
|
||||||
before do
|
before do
|
||||||
sign_in(admin)
|
sign_in(admin)
|
||||||
@ -89,7 +98,42 @@ RSpec.describe PublishedPagesController do
|
|||||||
|
|
||||||
it "defines correct css classes on body" do
|
it "defines correct css classes on body" do
|
||||||
get published_page.path
|
get published_page.path
|
||||||
expect(response.body).to include("<body class=\"published-page published-page-test topic-#{published_page.topic_id} recipes uncategorized\">")
|
expect(response.body).to include("<body class=\"published-page #{published_page.slug} topic-#{published_page.topic_id} recipes uncategorized\">")
|
||||||
|
end
|
||||||
|
|
||||||
|
context "login is required" do
|
||||||
|
before do
|
||||||
|
SiteSetting.login_required = true
|
||||||
|
SiteSetting.show_published_pages_login_required = false
|
||||||
|
end
|
||||||
|
|
||||||
|
context "a user is connected" do
|
||||||
|
before do
|
||||||
|
sign_in(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns 200" do
|
||||||
|
get published_page.path
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "no user connected" do
|
||||||
|
it "redirects to login page" do
|
||||||
|
expect(get(published_page.path)).to redirect_to("/login")
|
||||||
|
end
|
||||||
|
|
||||||
|
context "show public pages with login required is enabled" do
|
||||||
|
before do
|
||||||
|
SiteSetting.show_published_pages_login_required = true
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns 200" do
|
||||||
|
get published_page.path
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -113,6 +157,7 @@ RSpec.describe PublishedPagesController do
|
|||||||
expect(response).to be_successful
|
expect(response).to be_successful
|
||||||
expect(response.parsed_body['published_page']).to be_present
|
expect(response.parsed_body['published_page']).to be_present
|
||||||
expect(response.parsed_body['published_page']['slug']).to eq("i-hate-salt")
|
expect(response.parsed_body['published_page']['slug']).to eq("i-hate-salt")
|
||||||
|
expect(response.parsed_body['published_page']['public']).to eq(false)
|
||||||
|
|
||||||
expect(PublishedPage.exists?(topic_id: response.parsed_body['published_page']['id'])).to eq(true)
|
expect(PublishedPage.exists?(topic_id: response.parsed_body['published_page']['id'])).to eq(true)
|
||||||
expect(UserHistory.exists?(
|
expect(UserHistory.exists?(
|
||||||
@ -122,6 +167,16 @@ RSpec.describe PublishedPagesController do
|
|||||||
)).to be(true)
|
)).to be(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "allows to set public field" do
|
||||||
|
put "/pub/by-topic/#{topic.id}.json", params: { published_page: { slug: 'i-hate-salt', public: true } }
|
||||||
|
expect(response).to be_successful
|
||||||
|
expect(response.parsed_body['published_page']).to be_present
|
||||||
|
expect(response.parsed_body['published_page']['slug']).to eq("i-hate-salt")
|
||||||
|
expect(response.parsed_body['published_page']['public']).to eq(true)
|
||||||
|
|
||||||
|
expect(PublishedPage.exists?(topic_id: response.parsed_body['published_page']['id'])).to eq(true)
|
||||||
|
end
|
||||||
|
|
||||||
it "returns an error if the slug is already taken" do
|
it "returns an error if the slug is already taken" do
|
||||||
PublishedPage.create!(slug: 'i-hate-salt', topic: Fabricate(:topic))
|
PublishedPage.create!(slug: 'i-hate-salt', topic: Fabricate(:topic))
|
||||||
put "/pub/by-topic/#{topic.id}.json", params: { published_page: { slug: 'i-hate-salt' } }
|
put "/pub/by-topic/#{topic.id}.json", params: { published_page: { slug: 'i-hate-salt' } }
|
||||||
|
Loading…
Reference in New Issue
Block a user