diff --git a/app/assets/javascripts/embed-application.js.no-module.es6 b/app/assets/javascripts/embed-application.js.no-module.es6
index f8d9c082276..9cdda8267e1 100644
--- a/app/assets/javascripts/embed-application.js.no-module.es6
+++ b/app/assets/javascripts/embed-application.js.no-module.es6
@@ -24,17 +24,20 @@
window.onload = function() {
// get state info from data attribute
- var header = document.querySelector("header");
+ var embedState = document.querySelector("[data-embed-state]");
var state = "unknown";
- if (header) {
- state = header.getAttribute("data-embed-state");
+ var embedId = null;
+ if (embedState) {
+ state = embedState.getAttribute("data-embed-state");
+ embedId = embedState.getAttribute("data-embed-id");
}
// Send a post message with our loaded height and state
postUp({
type: "discourse-resize",
height: document["body"].offsetHeight,
- state: state
+ state,
+ embedId
});
var postLinks = document.querySelectorAll("a[data-link-to-post]"),
diff --git a/app/assets/stylesheets/embed.scss b/app/assets/stylesheets/embed.scss
index a48d0ac327e..6caaa12a9d7 100644
--- a/app/assets/stylesheets/embed.scss
+++ b/app/assets/stylesheets/embed.scss
@@ -175,3 +175,19 @@ aside.onebox {
div.lightbox-wrapper {
margin-bottom: 20px;
}
+
+.topics-list {
+ width: 100%;
+ .topic-list-item {
+ td {
+ padding: 0.5rem;
+ }
+
+ .main-link a {
+ color: $primary;
+ }
+ .main-link a:visited {
+ color: $primary-medium;
+ }
+ }
+}
diff --git a/app/controllers/embed_controller.rb b/app/controllers/embed_controller.rb
index 759e334d4fa..93c94175521 100644
--- a/app/controllers/embed_controller.rb
+++ b/app/controllers/embed_controller.rb
@@ -1,10 +1,12 @@
# frozen_string_literal: true
class EmbedController < ApplicationController
+ include TopicQueryParams
+
skip_before_action :check_xhr, :preload_json, :verify_authenticity_token
- before_action :ensure_embeddable, except: [ :info ]
- before_action :get_embeddable_css_class, except: [ :info ]
+ before_action :ensure_embeddable, except: [ :info, :topics ]
+ before_action :get_embeddable_css_class, except: [ :info, :topics ]
before_action :ensure_api_request, only: [ :info ]
layout 'embed'
@@ -16,7 +18,26 @@ class EmbedController < ApplicationController
@show_reason = true
@hosts = EmbeddableHost.all
end
- render 'embed_error'
+ render 'embed_error', status: 400
+ end
+
+ def topics
+ discourse_expires_in 1.minute
+
+ response.headers['X-Frame-Options'] = "ALLOWALL"
+ unless SiteSetting.embed_topics_list?
+ render 'embed_topics_error', status: 400
+ return
+ end
+
+ if @embed_id = params[:discourse_embed_id]
+ raise Discourse::InvalidParameters.new(:embed_id) unless @embed_id =~ /^de\-[a-zA-Z0-9]+$/
+ end
+
+ list_options = build_topic_list_options
+ list_options[:per_page] = params[:per_page].to_i if params.has_key?(:per_page)
+ topic_query = TopicQuery.new(current_user, list_options)
+ @list = topic_query.list_latest
end
def comments
diff --git a/app/controllers/list_controller.rb b/app/controllers/list_controller.rb
index 69a6c96d6a5..6d2bace3415 100644
--- a/app/controllers/list_controller.rb
+++ b/app/controllers/list_controller.rb
@@ -1,9 +1,11 @@
# frozen_string_literal: true
require_dependency 'topic_list_responder'
+require_dependency 'topic_query_params'
class ListController < ApplicationController
include TopicListResponder
+ include TopicQueryParams
skip_before_action :check_xhr
@@ -376,28 +378,6 @@ class ListController < ApplicationController
end
end
- def build_topic_list_options
- options = {}
- params[:tags] = [params[:tag_id].parameterize] if params[:tag_id].present? && guardian.can_tag_pms?
-
- TopicQuery.public_valid_options.each do |key|
- if params.key?(key)
- val = options[key] = params[key]
- if !TopicQuery.validate?(key, val)
- raise Discourse::InvalidParameters.new key
- end
- end
- end
-
- # hacky columns get special handling
- options[:topic_ids] = param_to_integer_list(:topic_ids)
- if options[:no_subcategories] == 'true'
- options[:no_subcategories] = true
- end
-
- options
- end
-
def list_target_user
if params[:user_id] && guardian.is_staff?
User.find(params[:user_id].to_i)
diff --git a/app/views/embed/embed_topics_error.html.erb b/app/views/embed/embed_topics_error.html.erb
new file mode 100644
index 00000000000..392e36ee6ee
--- /dev/null
+++ b/app/views/embed/embed_topics_error.html.erb
@@ -0,0 +1,8 @@
+
+ <%= t 'embed.error' %>
+ <%= link_to(image_tag(SiteSetting.site_logo_url, class: 'logo'), Discourse.base_url) %>
+
+
+
+ <%= t('embed.error_topics') %>
+
diff --git a/app/views/embed/topics.html.erb b/app/views/embed/topics.html.erb
new file mode 100644
index 00000000000..6f478eeb9e7
--- /dev/null
+++ b/app/views/embed/topics.html.erb
@@ -0,0 +1,11 @@
+<%- if @list && @list.topics.present? %>
+ data-embed-id="<%= @embed_id %>"<%- end %>>
+ <%- @list.topics.each do |t| %>
+
+
+ <%= t.title %>
+ |
+
+ <%- end %>
+
+<%- end %>
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index a3553d7a8b4..4dc3a024cc3 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -268,6 +268,7 @@ en:
continue: "Continue Discussion"
error: "Error Embedding"
referer: "Referer:"
+ error_topics: "The `embed topics list` site setting was not enabled"
mismatch: "The referer was either not sent, or did not match any of the following hosts:"
no_hosts: "No hosts were set up for embedding."
configure: "Configure Embedding"
@@ -1943,6 +1944,7 @@ en:
autohighlight_all_code: "Force apply code highlighting to all preformatted code blocks even when they didn't explicitly specify the language."
highlighted_languages: "Included syntax highlighting rules. (Warning: including too many languages may impact performance) see: https://highlightjs.org/static/demo for a demo"
+ embed_topics_list: "Support HTML embedding of topics lists"
embed_truncate: "Truncate the embedded posts."
embed_support_markdown: "Support Markdown formatting for embedded posts."
embed_whitelist_selector: "A comma separated list of CSS elements that are allowed in embeds."
diff --git a/config/routes.rb b/config/routes.rb
index 88db857f3a7..2ef2345bb95 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -711,6 +711,7 @@ Discourse::Application.routes.draw do
end
end
+ get 'embed/topics' => 'embed#topics'
get 'embed/comments' => 'embed#comments'
get 'embed/count' => 'embed#count'
get 'embed/info' => 'embed#info'
diff --git a/config/site_settings.yml b/config/site_settings.yml
index cfaf9719484..1f1c1ee366e 100644
--- a/config/site_settings.yml
+++ b/config/site_settings.yml
@@ -849,6 +849,7 @@ posting:
choices:
- 4-spaces-indent
- code-fences
+ embed_topics_list: false
embed_truncate:
default: true
embed_support_markdown:
diff --git a/lib/topic_query_params.rb b/lib/topic_query_params.rb
new file mode 100644
index 00000000000..e1bb23cb9f7
--- /dev/null
+++ b/lib/topic_query_params.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module TopicQueryParams
+ def build_topic_list_options
+ options = {}
+ params[:tags] = [params[:tag_id].parameterize] if params[:tag_id].present? && guardian.can_tag_pms?
+
+ TopicQuery.public_valid_options.each do |key|
+ if params.key?(key)
+ val = options[key] = params[key]
+ if !TopicQuery.validate?(key, val)
+ raise Discourse::InvalidParameters.new key
+ end
+ end
+ end
+
+ # hacky columns get special handling
+ options[:topic_ids] = param_to_integer_list(:topic_ids)
+ if options[:no_subcategories] == 'true'
+ options[:no_subcategories] = true
+ end
+
+ options
+ end
+end
diff --git a/public/javascripts/embed-topics.js b/public/javascripts/embed-topics.js
new file mode 100644
index 00000000000..3ccbf8d6ea3
--- /dev/null
+++ b/public/javascripts/embed-topics.js
@@ -0,0 +1,47 @@
+(function() {
+ function postMessageReceived(e) {
+ if (!e) {
+ return;
+ }
+
+ if (e.data && e.data.type === "discourse-resize" && e.data.embedId) {
+ var elem = document.getElementById(e.data.embedId);
+ if (elem) {
+ elem.height = e.data.height + "px";
+ }
+ }
+ }
+ window.addEventListener("message", postMessageReceived, false);
+
+ document.addEventListener("DOMContentLoaded", function(event) {
+ var lists = document.querySelectorAll("d-topics-list");
+
+ for (var i = 0; i < lists.length; i++) {
+ var list = lists[i];
+ var url = list.getAttribute("discourse-url");
+ if (!url || url.length === 0) {
+ console.error("Error, `data-discourse-url` was not found");
+ continue;
+ }
+ var frameId =
+ "de-" +
+ Math.random()
+ .toString(36)
+ .substr(2, 9);
+ var params = ["discourse_embed_id=" + frameId];
+ list.removeAttribute("discourse-url");
+
+ for (var j = 0; j < list.attributes.length; j++) {
+ var attr = list.attributes[j];
+ params.push(attr.name.replace("-", "_") + "=" + attr.value);
+ }
+
+ var iframe = document.createElement("iframe");
+ iframe.src = url + "/embed/topics?" + params.join("&");
+ iframe.id = frameId;
+ iframe.frameBorder = 0;
+ iframe.scrolling = "no";
+ list.appendChild(iframe);
+ }
+ });
+})();
diff --git a/spec/requests/embed_controller_spec.rb b/spec/requests/embed_controller_spec.rb
index 1ee75c2b4e4..8d945dcb448 100644
--- a/spec/requests/embed_controller_spec.rb
+++ b/spec/requests/embed_controller_spec.rb
@@ -70,6 +70,33 @@ describe EmbedController do
end
end
+ context "#topics" do
+ it "raises an error when not enabled" do
+ get '/embed/topics?embed_id=de-1234'
+ expect(response.status).to eq(400)
+ end
+
+ context "when enabled" do
+ before do
+ SiteSetting.embed_topics_list = true
+ end
+
+ it "raises an error with a weird id" do
+ get '/embed/topics?discourse_embed_id=../asdf/-1234', headers: headers
+ expect(response.status).to eq(400)
+ end
+
+ it "returns a list of topics" do
+ topic = Fabricate(:topic)
+ get '/embed/topics?discourse_embed_id=de-1234', headers: headers
+ expect(response.status).to eq(200)
+ expect(response.headers['X-Frame-Options']).to eq("ALLOWALL")
+ expect(response.body).to match("data-embed-id=\"de-1234\"")
+ expect(response.body).to match("data-topic-id=\"#{topic.id}\"")
+ end
+ end
+ end
+
context "with a host" do
let!(:embeddable_host) { Fabricate(:embeddable_host) }
let(:headers) { { 'REFERER' => embed_url } }