mirror of
https://github.com/codeninjasllc/discourse.git
synced 2024-11-27 09:36:19 -05:00
FEATURE: Can edit category/host relationships for embedding
This commit is contained in:
parent
913c3d6f63
commit
d1c69189f3
36 changed files with 449 additions and 127 deletions
7
app/assets/javascripts/admin/adapters/embedding.js.es6
Normal file
7
app/assets/javascripts/admin/adapters/embedding.js.es6
Normal file
|
@ -0,0 +1,7 @@
|
|||
import RestAdapter from 'discourse/adapters/rest';
|
||||
|
||||
export default RestAdapter.extend({
|
||||
pathFor() {
|
||||
return "/admin/customize/embedding";
|
||||
}
|
||||
});
|
|
@ -0,0 +1,63 @@
|
|||
import { bufferedProperty } from 'discourse/mixins/buffered-content';
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
import { on, observes } from 'ember-addons/ember-computed-decorators';
|
||||
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||
|
||||
export default Ember.Component.extend(bufferedProperty('host'), {
|
||||
editToggled: false,
|
||||
tagName: 'tr',
|
||||
categoryId: null,
|
||||
|
||||
editing: Ember.computed.or('host.isNew', 'editToggled'),
|
||||
|
||||
@on('didInsertElement')
|
||||
@observes('editing')
|
||||
_focusOnInput() {
|
||||
Ember.run.schedule('afterRender', () => { this.$('.host-name').focus(); });
|
||||
},
|
||||
|
||||
@computed('buffered.host', 'host.isSaving')
|
||||
cantSave(host, isSaving) {
|
||||
return isSaving || Ember.isEmpty(host);
|
||||
},
|
||||
|
||||
actions: {
|
||||
edit() {
|
||||
this.set('categoryId', this.get('host.category.id'));
|
||||
this.set('editToggled', true);
|
||||
},
|
||||
|
||||
save() {
|
||||
if (this.get('cantSave')) { return; }
|
||||
|
||||
const props = this.get('buffered').getProperties('host');
|
||||
props.category_id = this.get('categoryId');
|
||||
|
||||
const host = this.get('host');
|
||||
host.save(props).then(() => {
|
||||
host.set('category', Discourse.Category.findById(this.get('categoryId')));
|
||||
this.set('editToggled', false);
|
||||
}).catch(popupAjaxError);
|
||||
},
|
||||
|
||||
delete() {
|
||||
bootbox.confirm(I18n.t('admin.embedding.confirm_delete'), (result) => {
|
||||
if (result) {
|
||||
this.get('host').destroyRecord().then(() => {
|
||||
this.sendAction('deleteHost', this.get('host'));
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
cancel() {
|
||||
const host = this.get('host');
|
||||
if (host.get('isNew')) {
|
||||
this.sendAction('deleteHost', host);
|
||||
} else {
|
||||
this.rollbackBuffer();
|
||||
this.set('editToggled', false);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
|
@ -0,0 +1,18 @@
|
|||
export default Ember.Controller.extend({
|
||||
embedding: null,
|
||||
|
||||
actions: {
|
||||
saveChanges() {
|
||||
this.get('embedding').update({});
|
||||
},
|
||||
|
||||
addHost() {
|
||||
const host = this.store.createRecord('embeddable-host');
|
||||
this.get('embedding.embeddable_hosts').pushObject(host);
|
||||
},
|
||||
|
||||
deleteHost(host) {
|
||||
this.get('embedding.embeddable_hosts').removeObject(host);
|
||||
}
|
||||
}
|
||||
});
|
|
@ -0,0 +1,9 @@
|
|||
export default Ember.Route.extend({
|
||||
model() {
|
||||
return this.store.find('embedding');
|
||||
},
|
||||
|
||||
setupController(controller, model) {
|
||||
controller.set('embedding', model);
|
||||
}
|
||||
});
|
|
@ -27,6 +27,7 @@ export default {
|
|||
this.resource('adminUserFields', { path: '/user_fields' });
|
||||
this.resource('adminEmojis', { path: '/emojis' });
|
||||
this.resource('adminPermalinks', { path: '/permalinks' });
|
||||
this.resource('adminEmbedding', { path: '/embedding' });
|
||||
});
|
||||
this.route('api');
|
||||
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
{{#if editing}}
|
||||
<td>
|
||||
{{input value=buffered.host placeholder="example.com" enter="save" class="host-name"}}
|
||||
</td>
|
||||
<td>
|
||||
{{category-chooser value=categoryId}}
|
||||
</td>
|
||||
<td>
|
||||
{{d-button icon="check" action="save" class="btn-primary" disabled=cantSave}}
|
||||
{{d-button icon="times" action="cancel" class="btn-danger" disabled=host.isSaving}}
|
||||
</td>
|
||||
{{else}}
|
||||
<td>{{host.host}}</td>
|
||||
<td>{{category-badge host.category}}</td>
|
||||
<td>
|
||||
{{d-button icon="pencil" action="edit"}}
|
||||
{{d-button icon="trash-o" action="delete" class='btn-danger'}}
|
||||
</td>
|
||||
{{/if}}
|
|
@ -5,6 +5,7 @@
|
|||
{{nav-item route='adminUserFields' label='admin.user_fields.title'}}
|
||||
{{nav-item route='adminEmojis' label='admin.emoji.title'}}
|
||||
{{nav-item route='adminPermalinks' label='admin.permalink.title'}}
|
||||
{{nav-item route='adminEmbedding' label='admin.embedding.title'}}
|
||||
{{/admin-nav}}
|
||||
|
||||
<div class="admin-container">
|
||||
|
|
15
app/assets/javascripts/admin/templates/embedding.hbs
Normal file
15
app/assets/javascripts/admin/templates/embedding.hbs
Normal file
|
@ -0,0 +1,15 @@
|
|||
{{#if embedding.embeddable_hosts}}
|
||||
<table>
|
||||
<tr>
|
||||
<th style='width: 50%'>{{i18n "admin.embedding.host"}}</th>
|
||||
<th style='width: 30%'>{{i18n "admin.embedding.category"}}</th>
|
||||
<th style='width: 20%'> </th>
|
||||
</tr>
|
||||
{{#each embedding.embeddable_hosts as |host|}}
|
||||
{{embeddable-host host=host deleteHost="deleteHost"}}
|
||||
{{/each}}
|
||||
</table>
|
||||
{{/if}}
|
||||
|
||||
{{d-button label="admin.embedding.add_host" action="addHost" icon="plus" class="btn-primary"}}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
const ADMIN_MODELS = ['plugin', 'site-customization'];
|
||||
const ADMIN_MODELS = ['plugin', 'site-customization', 'embeddable-host'];
|
||||
|
||||
export function Result(payload, responseJson) {
|
||||
this.payload = payload;
|
||||
|
@ -19,7 +19,7 @@ function rethrow(error) {
|
|||
export default Ember.Object.extend({
|
||||
|
||||
basePath(store, type) {
|
||||
if (ADMIN_MODELS.indexOf(type) !== -1) { return "/admin/"; }
|
||||
if (ADMIN_MODELS.indexOf(type.replace('_', '-')) !== -1) { return "/admin/"; }
|
||||
return "/";
|
||||
},
|
||||
|
||||
|
|
|
@ -189,15 +189,25 @@ export default Ember.Object.extend({
|
|||
_hydrateEmbedded(type, obj, root) {
|
||||
const self = this;
|
||||
Object.keys(obj).forEach(function(k) {
|
||||
const m = /(.+)\_id$/.exec(k);
|
||||
const m = /(.+)\_id(s?)$/.exec(k);
|
||||
if (m) {
|
||||
const subType = m[1];
|
||||
|
||||
if (m[2]) {
|
||||
const hydrated = obj[k].map(function(id) {
|
||||
return self._lookupSubType(subType, type, id, root);
|
||||
});
|
||||
obj[self.pluralize(subType)] = hydrated || [];
|
||||
delete obj[k];
|
||||
} else {
|
||||
const hydrated = self._lookupSubType(subType, type, obj[k], root);
|
||||
if (hydrated) {
|
||||
obj[subType] = hydrated;
|
||||
delete obj[k];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
|
34
app/controllers/admin/embeddable_hosts_controller.rb
Normal file
34
app/controllers/admin/embeddable_hosts_controller.rb
Normal file
|
@ -0,0 +1,34 @@
|
|||
class Admin::EmbeddableHostsController < Admin::AdminController
|
||||
|
||||
before_filter :ensure_logged_in, :ensure_staff
|
||||
|
||||
def create
|
||||
save_host(EmbeddableHost.new)
|
||||
end
|
||||
|
||||
def update
|
||||
host = EmbeddableHost.where(id: params[:id]).first
|
||||
save_host(host)
|
||||
end
|
||||
|
||||
def destroy
|
||||
host = EmbeddableHost.where(id: params[:id]).first
|
||||
host.destroy
|
||||
render json: success_json
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def save_host(host)
|
||||
host.host = params[:embeddable_host][:host]
|
||||
host.category_id = params[:embeddable_host][:category_id]
|
||||
host.category_id = SiteSetting.uncategorized_category_id if host.category_id.blank?
|
||||
|
||||
if host.save
|
||||
render_serialized(host, EmbeddableHostSerializer, root: 'embeddable_host', rest_serializer: true)
|
||||
else
|
||||
render_json_error(host)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
21
app/controllers/admin/embedding_controller.rb
Normal file
21
app/controllers/admin/embedding_controller.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
class Admin::EmbeddingController < Admin::AdminController
|
||||
|
||||
before_filter :ensure_logged_in, :ensure_staff, :fetch_embedding
|
||||
|
||||
def show
|
||||
render_serialized(@embedding, EmbeddingSerializer, root: 'embedding', rest_serializer: true)
|
||||
end
|
||||
|
||||
def update
|
||||
render_serialized(@embedding, EmbeddingSerializer, root: 'embedding', rest_serializer: true)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def fetch_embedding
|
||||
@embedding = OpenStruct.new({
|
||||
id: 'default',
|
||||
embeddable_hosts: EmbeddableHost.all.order(:host)
|
||||
})
|
||||
end
|
||||
end
|
|
@ -58,8 +58,7 @@ class EmbedController < ApplicationController
|
|||
def ensure_embeddable
|
||||
|
||||
if !(Rails.env.development? && current_user.try(:admin?))
|
||||
raise Discourse::InvalidAccess.new('embeddable hosts not set') if SiteSetting.embeddable_hosts.blank?
|
||||
raise Discourse::InvalidAccess.new('invalid referer host') unless SiteSetting.allows_embeddable_host?(request.referer)
|
||||
raise Discourse::InvalidAccess.new('invalid referer host') unless EmbeddableHost.host_allowed?(request.referer)
|
||||
end
|
||||
|
||||
response.headers['X-Frame-Options'] = "ALLOWALL"
|
||||
|
|
24
app/models/embeddable_host.rb
Normal file
24
app/models/embeddable_host.rb
Normal file
|
@ -0,0 +1,24 @@
|
|||
class EmbeddableHost < ActiveRecord::Base
|
||||
validates_format_of :host, :with => /\A[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?\Z/i
|
||||
belongs_to :category
|
||||
|
||||
before_validation do
|
||||
self.host.sub!(/^https?:\/\//, '')
|
||||
self.host.sub!(/\/.*$/, '')
|
||||
end
|
||||
|
||||
def self.record_for_host(host)
|
||||
uri = URI(host) rescue nil
|
||||
return false unless uri.present?
|
||||
|
||||
host = uri.host
|
||||
return false unless host.present?
|
||||
|
||||
where("lower(host) = ?", host).first
|
||||
end
|
||||
|
||||
def self.host_allowed?(host)
|
||||
record_for_host(host).present?
|
||||
end
|
||||
|
||||
end
|
|
@ -68,20 +68,6 @@ class SiteSetting < ActiveRecord::Base
|
|||
@anonymous_menu_items ||= Set.new Discourse.anonymous_filters.map(&:to_s)
|
||||
end
|
||||
|
||||
def self.allows_embeddable_host?(host)
|
||||
return false if embeddable_hosts.blank?
|
||||
uri = URI(host) rescue nil
|
||||
return false unless uri.present?
|
||||
|
||||
host = uri.host
|
||||
return false unless host.present?
|
||||
|
||||
!!embeddable_hosts.split("\n").detect {|h| h.sub(/^https?\:\/\//, '') == host }
|
||||
|
||||
hosts = embeddable_hosts.split("\n").map {|h| (URI(h).host rescue nil) || h }
|
||||
!!hosts.detect {|h| h == host}
|
||||
end
|
||||
|
||||
def self.anonymous_homepage
|
||||
top_menu_items.map { |item| item.name }
|
||||
.select { |item| anonymous_menu_items.include?(item) }
|
||||
|
|
|
@ -866,7 +866,7 @@ class Topic < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def expandable_first_post?
|
||||
SiteSetting.embeddable_hosts.present? && SiteSetting.embed_truncate? && has_topic_embed?
|
||||
SiteSetting.embed_truncate? && has_topic_embed?
|
||||
end
|
||||
|
||||
TIME_TO_FIRST_RESPONSE_SQL ||= <<-SQL
|
||||
|
|
|
@ -33,12 +33,14 @@ class TopicEmbed < ActiveRecord::Base
|
|||
# If there is no embed, create a topic, post and the embed.
|
||||
if embed.blank?
|
||||
Topic.transaction do
|
||||
eh = EmbeddableHost.record_for_host(url)
|
||||
|
||||
creator = PostCreator.new(user,
|
||||
title: title,
|
||||
raw: absolutize_urls(url, contents),
|
||||
skip_validations: true,
|
||||
cook_method: Post.cook_methods[:raw_html],
|
||||
category: SiteSetting.embed_category)
|
||||
category: eh.try(:category_id))
|
||||
post = creator.create
|
||||
if post.present?
|
||||
TopicEmbed.create!(topic_id: post.topic_id,
|
||||
|
|
16
app/serializers/embeddable_host_serializer.rb
Normal file
16
app/serializers/embeddable_host_serializer.rb
Normal file
|
@ -0,0 +1,16 @@
|
|||
class EmbeddableHostSerializer < ApplicationSerializer
|
||||
attributes :id, :host, :category_id
|
||||
|
||||
def id
|
||||
object.id
|
||||
end
|
||||
|
||||
def host
|
||||
object.host
|
||||
end
|
||||
|
||||
def category_id
|
||||
object.category_id
|
||||
end
|
||||
end
|
||||
|
8
app/serializers/embedding_serializer.rb
Normal file
8
app/serializers/embedding_serializer.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
class EmbeddingSerializer < ApplicationSerializer
|
||||
attributes :id
|
||||
has_many :embeddable_hosts, serializer: EmbeddableHostSerializer, embed: :ids
|
||||
|
||||
def id
|
||||
object.id
|
||||
end
|
||||
end
|
|
@ -2491,6 +2491,14 @@ en:
|
|||
image: "Image"
|
||||
delete_confirm: "Are you sure you want to delete the :%{name}: emoji?"
|
||||
|
||||
embedding:
|
||||
confirm_delete: "Are you sure you want to delete that host?"
|
||||
title: "Embedding"
|
||||
host: "Allowed Hosts"
|
||||
edit: "edit"
|
||||
category: "Post to Category"
|
||||
add_host: "Add Host"
|
||||
|
||||
permalink:
|
||||
title: "Permalinks"
|
||||
url: "URL"
|
||||
|
|
|
@ -1164,13 +1164,11 @@ 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 langauges may impact performance) see: https://highlightjs.org/static/demo/ for a demo"
|
||||
|
||||
embeddable_hosts: "Host(s) that can embed the comments from this Discourse forum. Hostname only, do not begin with http://"
|
||||
feed_polling_enabled: "EMBEDDING ONLY: Whether to embed a RSS/ATOM feed as posts."
|
||||
feed_polling_url: "EMBEDDING ONLY: URL of RSS/ATOM feed to embed."
|
||||
embed_by_username: "Discourse username of the user who creates the embedded topics."
|
||||
embed_username_key_from_feed: "Key to pull discourse username from feed."
|
||||
embed_truncate: "Truncate the embedded posts."
|
||||
embed_category: "Category of embedded topics."
|
||||
embed_post_limit: "Maximum number of posts to embed."
|
||||
embed_whitelist_selector: "CSS selector for elements that are allowed in embeds."
|
||||
embed_blacklist_selector: "CSS selector for elements that are removed from embeds."
|
||||
|
|
|
@ -135,6 +135,8 @@ Discourse::Application.routes.draw do
|
|||
get "customize/css_html/:id/:section" => "site_customizations#index", constraints: AdminConstraint.new
|
||||
get "customize/colors" => "color_schemes#index", constraints: AdminConstraint.new
|
||||
get "customize/permalinks" => "permalinks#index", constraints: AdminConstraint.new
|
||||
get "customize/embedding" => "embedding#show", constraints: AdminConstraint.new
|
||||
put "customize/embedding" => "embedding#update", constraints: AdminConstraint.new
|
||||
get "flags" => "flags#index"
|
||||
get "flags/:filter" => "flags#index"
|
||||
post "flags/agree/:id" => "flags#agree"
|
||||
|
@ -148,6 +150,7 @@ Discourse::Application.routes.draw do
|
|||
resources :emojis, constraints: AdminConstraint.new
|
||||
end
|
||||
|
||||
resources :embeddable_hosts, constraints: AdminConstraint.new
|
||||
resources :color_schemes, constraints: AdminConstraint.new
|
||||
|
||||
resources :permalinks, constraints: AdminConstraint.new
|
||||
|
|
|
@ -759,16 +759,12 @@ developer:
|
|||
default: false
|
||||
|
||||
embedding:
|
||||
embeddable_hosts:
|
||||
default: ''
|
||||
type: host_list
|
||||
feed_polling_enabled: false
|
||||
feed_polling_url: ''
|
||||
embed_by_username:
|
||||
default: ''
|
||||
type: username
|
||||
embed_username_key_from_feed: ''
|
||||
embed_category: ''
|
||||
embed_post_limit: 100
|
||||
embed_truncate: false
|
||||
embed_whitelist_selector: ''
|
||||
|
|
33
db/migrate/20150818190757_create_embeddable_hosts.rb
Normal file
33
db/migrate/20150818190757_create_embeddable_hosts.rb
Normal file
|
@ -0,0 +1,33 @@
|
|||
class CreateEmbeddableHosts < ActiveRecord::Migration
|
||||
def change
|
||||
create_table :embeddable_hosts, force: true do |t|
|
||||
t.string :host, null: false
|
||||
t.integer :category_id, null: false
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
category_id = execute("SELECT c.id FROM categories AS c
|
||||
INNER JOIN site_settings AS s ON s.value = c.name
|
||||
WHERE s.name = 'embed_category'")[0]['id'].to_i
|
||||
|
||||
|
||||
if category_id == 0
|
||||
category_id = execute("SELECT value FROM site_settings WHERE name = 'uncategorized_category_id'")[0]['value'].to_i
|
||||
end
|
||||
|
||||
embeddable_hosts = execute("SELECT value FROM site_settings WHERE name = 'embeddable_hosts'")
|
||||
if embeddable_hosts && embeddable_hosts.cmd_tuples > 0
|
||||
val = embeddable_hosts[0]['value']
|
||||
if val.present?
|
||||
records = val.split("\n")
|
||||
if records.present?
|
||||
records.each do |h|
|
||||
execute "INSERT INTO embeddable_hosts (host, category_id, created_at, updated_at) VALUES ('#{h}', #{category_id}, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
execute "DELETE FROM site_settings WHERE name IN ('embeddable_hosts', 'embed_category')"
|
||||
end
|
||||
end
|
|
@ -13,7 +13,7 @@ class TopicRetriever
|
|||
private
|
||||
|
||||
def invalid_host?
|
||||
!SiteSetting.allows_embeddable_host?(@embed_url)
|
||||
!EmbeddableHost.host_allowed?(@embed_url)
|
||||
end
|
||||
|
||||
def retrieved_recently?
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Admin::EmbeddableHostsController do
|
||||
|
||||
it "is a subclass of AdminController" do
|
||||
expect(Admin::EmbeddableHostsController < Admin::AdminController).to eq(true)
|
||||
end
|
||||
|
||||
end
|
9
spec/controllers/admin/embedding_controller_spec.rb
Normal file
9
spec/controllers/admin/embedding_controller_spec.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Admin::EmbeddingController do
|
||||
|
||||
it "is a subclass of AdminController" do
|
||||
expect(Admin::EmbeddingController < Admin::AdminController).to eq(true)
|
||||
end
|
||||
|
||||
end
|
|
@ -11,7 +11,6 @@ describe EmbedController do
|
|||
end
|
||||
|
||||
it "raises an error with a missing host" do
|
||||
SiteSetting.embeddable_hosts = nil
|
||||
get :comments, embed_url: embed_url
|
||||
expect(response).not_to be_success
|
||||
end
|
||||
|
@ -19,7 +18,7 @@ describe EmbedController do
|
|||
context "by topic id" do
|
||||
|
||||
before do
|
||||
SiteSetting.embeddable_hosts = host
|
||||
Fabricate(:embeddable_host)
|
||||
controller.request.stubs(:referer).returns('http://eviltrout.com/some-page')
|
||||
end
|
||||
|
||||
|
@ -31,9 +30,7 @@ describe EmbedController do
|
|||
end
|
||||
|
||||
context "with a host" do
|
||||
before do
|
||||
SiteSetting.embeddable_hosts = host
|
||||
end
|
||||
let!(:embeddable_host) { Fabricate(:embeddable_host) }
|
||||
|
||||
it "raises an error with no referer" do
|
||||
get :comments, embed_url: embed_url
|
||||
|
@ -68,7 +65,9 @@ describe EmbedController do
|
|||
|
||||
context "with multiple hosts" do
|
||||
before do
|
||||
SiteSetting.embeddable_hosts = "#{host}\nhttp://discourse.org\nhttps://example.com/1234"
|
||||
Fabricate(:embeddable_host)
|
||||
Fabricate(:embeddable_host, host: 'http://discourse.org')
|
||||
Fabricate(:embeddable_host, host: 'https://example.com/1234')
|
||||
end
|
||||
|
||||
context "success" do
|
||||
|
|
|
@ -1,27 +1,4 @@
|
|||
Fabricator(:category) do
|
||||
name { sequence(:name) { |n| "Amazing Category #{n}" } }
|
||||
user
|
||||
end
|
||||
|
||||
Fabricator(:diff_category, from: :category) do
|
||||
name "Different Category"
|
||||
user
|
||||
end
|
||||
|
||||
Fabricator(:happy_category, from: :category) do
|
||||
name 'Happy Category'
|
||||
slug 'happy'
|
||||
user
|
||||
end
|
||||
|
||||
Fabricator(:private_category, from: :category) do
|
||||
transient :group
|
||||
|
||||
name 'Private Category'
|
||||
slug 'private'
|
||||
user
|
||||
after_build do |cat, transients|
|
||||
cat.update!(read_restricted: true)
|
||||
cat.category_groups.build(group_id: transients[:group].id, permission_type: CategoryGroup.permission_types[:full])
|
||||
end
|
||||
Fabricator(:embeddable_host) do
|
||||
host "eviltrout.com"
|
||||
category
|
||||
end
|
||||
|
|
27
spec/fabricators/embeddable_host_fabricator.rb
Normal file
27
spec/fabricators/embeddable_host_fabricator.rb
Normal file
|
@ -0,0 +1,27 @@
|
|||
Fabricator(:category) do
|
||||
name { sequence(:name) { |n| "Amazing Category #{n}" } }
|
||||
user
|
||||
end
|
||||
|
||||
Fabricator(:diff_category, from: :category) do
|
||||
name "Different Category"
|
||||
user
|
||||
end
|
||||
|
||||
Fabricator(:happy_category, from: :category) do
|
||||
name 'Happy Category'
|
||||
slug 'happy'
|
||||
user
|
||||
end
|
||||
|
||||
Fabricator(:private_category, from: :category) do
|
||||
transient :group
|
||||
|
||||
name 'Private Category'
|
||||
slug 'private'
|
||||
user
|
||||
after_build do |cat, transients|
|
||||
cat.update!(read_restricted: true)
|
||||
cat.category_groups.build(group_id: transients[:group].id, permission_type: CategoryGroup.permission_types[:full])
|
||||
end
|
||||
end
|
40
spec/models/embeddable_host_spec.rb
Normal file
40
spec/models/embeddable_host_spec.rb
Normal file
|
@ -0,0 +1,40 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe EmbeddableHost do
|
||||
|
||||
it "trims http" do
|
||||
eh = EmbeddableHost.new(host: 'http://example.com')
|
||||
expect(eh).to be_valid
|
||||
expect(eh.host).to eq('example.com')
|
||||
end
|
||||
|
||||
it "trims https" do
|
||||
eh = EmbeddableHost.new(host: 'https://example.com')
|
||||
expect(eh).to be_valid
|
||||
expect(eh.host).to eq('example.com')
|
||||
end
|
||||
|
||||
it "trims paths" do
|
||||
eh = EmbeddableHost.new(host: 'https://example.com/1234/45')
|
||||
expect(eh).to be_valid
|
||||
expect(eh.host).to eq('example.com')
|
||||
end
|
||||
|
||||
describe "allows_embeddable_host" do
|
||||
let!(:host) { Fabricate(:embeddable_host) }
|
||||
|
||||
it 'works as expected' do
|
||||
expect(EmbeddableHost.host_allowed?('http://eviltrout.com')).to eq(true)
|
||||
expect(EmbeddableHost.host_allowed?('https://eviltrout.com')).to eq(true)
|
||||
expect(EmbeddableHost.host_allowed?('https://not-eviltrout.com')).to eq(false)
|
||||
end
|
||||
|
||||
it 'works with multiple hosts' do
|
||||
Fabricate(:embeddable_host, host: 'discourse.org')
|
||||
expect(EmbeddableHost.host_allowed?('http://eviltrout.com')).to eq(true)
|
||||
expect(EmbeddableHost.host_allowed?('http://discourse.org')).to eq(true)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
|
@ -4,36 +4,6 @@ require_dependency 'site_setting_extension'
|
|||
|
||||
describe SiteSetting do
|
||||
|
||||
describe "allows_embeddable_host" do
|
||||
it 'works as expected' do
|
||||
SiteSetting.embeddable_hosts = 'eviltrout.com'
|
||||
expect(SiteSetting.allows_embeddable_host?('http://eviltrout.com')).to eq(true)
|
||||
expect(SiteSetting.allows_embeddable_host?('https://eviltrout.com')).to eq(true)
|
||||
expect(SiteSetting.allows_embeddable_host?('https://not-eviltrout.com')).to eq(false)
|
||||
end
|
||||
|
||||
it 'works with a http host' do
|
||||
SiteSetting.embeddable_hosts = 'http://eviltrout.com'
|
||||
expect(SiteSetting.allows_embeddable_host?('http://eviltrout.com')).to eq(true)
|
||||
expect(SiteSetting.allows_embeddable_host?('https://eviltrout.com')).to eq(true)
|
||||
expect(SiteSetting.allows_embeddable_host?('https://not-eviltrout.com')).to eq(false)
|
||||
end
|
||||
|
||||
it 'works with a https host' do
|
||||
SiteSetting.embeddable_hosts = 'https://eviltrout.com'
|
||||
expect(SiteSetting.allows_embeddable_host?('http://eviltrout.com')).to eq(true)
|
||||
expect(SiteSetting.allows_embeddable_host?('https://eviltrout.com')).to eq(true)
|
||||
expect(SiteSetting.allows_embeddable_host?('https://not-eviltrout.com')).to eq(false)
|
||||
end
|
||||
|
||||
it 'works with multiple hosts' do
|
||||
SiteSetting.embeddable_hosts = "https://eviltrout.com\nhttps://discourse.org"
|
||||
expect(SiteSetting.allows_embeddable_host?('http://eviltrout.com')).to eq(true)
|
||||
expect(SiteSetting.allows_embeddable_host?('http://discourse.org')).to eq(true)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'topic_title_length' do
|
||||
it 'returns a range of min/max topic title length' do
|
||||
expect(SiteSetting.topic_title_length).to eq(
|
||||
|
|
|
@ -12,6 +12,7 @@ describe TopicEmbed do
|
|||
let(:title) { "How to turn a fish from good to evil in 30 seconds" }
|
||||
let(:url) { 'http://eviltrout.com/123' }
|
||||
let(:contents) { "hello world new post <a href='/hello'>hello</a> <img src='/images/wat.jpg'>" }
|
||||
let!(:embeddable_host) { Fabricate(:embeddable_host) }
|
||||
|
||||
it "returns nil when the URL is malformed" do
|
||||
expect(TopicEmbed.import(user, "invalid url", title, contents)).to eq(nil)
|
||||
|
@ -33,6 +34,8 @@ describe TopicEmbed do
|
|||
|
||||
expect(post.topic.has_topic_embed?).to eq(true)
|
||||
expect(TopicEmbed.where(topic_id: post.topic_id)).to be_present
|
||||
|
||||
expect(post.topic.category).to eq(embeddable_host.category)
|
||||
end
|
||||
|
||||
it "Supports updating the post" do
|
||||
|
|
|
@ -1238,7 +1238,7 @@ describe Topic do
|
|||
it "doesn't return topics from muted categories" do
|
||||
user = Fabricate(:user)
|
||||
category = Fabricate(:category)
|
||||
topic = Fabricate(:topic, category: category)
|
||||
Fabricate(:topic, category: category)
|
||||
|
||||
CategoryUser.set_notification_level_for_category(user, CategoryUser.notification_levels[:muted], category.id)
|
||||
|
||||
|
@ -1247,7 +1247,7 @@ describe Topic do
|
|||
|
||||
it "doesn't return topics from TL0 users" do
|
||||
new_user = Fabricate(:user, trust_level: 0)
|
||||
topic = Fabricate(:topic, user_id: new_user.id)
|
||||
Fabricate(:topic, user_id: new_user.id)
|
||||
|
||||
expect(Topic.for_digest(user, 1.year.ago, top_order: true)).to be_blank
|
||||
end
|
||||
|
@ -1397,10 +1397,16 @@ describe Topic do
|
|||
end
|
||||
|
||||
describe "expandable_first_post?" do
|
||||
|
||||
let(:topic) { Fabricate.build(:topic) }
|
||||
|
||||
it "is false if embeddable_host is blank" do
|
||||
expect(topic.expandable_first_post?).to eq(false)
|
||||
end
|
||||
|
||||
describe 'with an emeddable host' do
|
||||
before do
|
||||
SiteSetting.embeddable_hosts = "http://eviltrout.com"
|
||||
Fabricate(:embeddable_host)
|
||||
SiteSetting.embed_truncate = true
|
||||
topic.stubs(:has_topic_embed?).returns(true)
|
||||
end
|
||||
|
@ -1408,12 +1414,6 @@ describe Topic do
|
|||
it "is true with the correct settings and topic_embed" do
|
||||
expect(topic.expandable_first_post?).to eq(true)
|
||||
end
|
||||
|
||||
it "is false if embeddable_host is blank" do
|
||||
SiteSetting.embeddable_hosts = nil
|
||||
expect(topic.expandable_first_post?).to eq(false)
|
||||
end
|
||||
|
||||
it "is false if embed_truncate? is false" do
|
||||
SiteSetting.embed_truncate = false
|
||||
expect(topic.expandable_first_post?).to eq(false)
|
||||
|
@ -1425,6 +1425,8 @@ describe Topic do
|
|||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
it "has custom fields" do
|
||||
topic = Fabricate(:topic)
|
||||
expect(topic.custom_fields["a"]).to eq(nil)
|
||||
|
|
|
@ -39,13 +39,17 @@ const _moreWidgets = [
|
|||
{id: 224, name: 'Good Repellant'}
|
||||
];
|
||||
|
||||
const fruits = [{id: 1, name: 'apple', farmer_id: 1, category_id: 4},
|
||||
{id: 2, name: 'banana', farmer_id: 1, category_id: 3},
|
||||
{id: 3, name: 'grape', farmer_id: 2, category_id: 5}];
|
||||
const fruits = [{id: 1, name: 'apple', farmer_id: 1, color_ids: [1,2], category_id: 4},
|
||||
{id: 2, name: 'banana', farmer_id: 1, color_ids: [3], category_id: 3},
|
||||
{id: 3, name: 'grape', farmer_id: 2, color_ids: [2], category_id: 5}];
|
||||
|
||||
const farmers = [{id: 1, name: 'Old MacDonald'},
|
||||
{id: 2, name: 'Luke Skywalker'}];
|
||||
|
||||
const colors = [{id: 1, name: 'Red'},
|
||||
{id: 2, name: 'Green'},
|
||||
{id: 3, name: 'Yellow'}];
|
||||
|
||||
function loggedIn() {
|
||||
return !!Discourse.User.current();
|
||||
}
|
||||
|
@ -221,12 +225,11 @@ export default function() {
|
|||
|
||||
this.get('/fruits/:id', function() {
|
||||
const fruit = fruits[0];
|
||||
|
||||
return response({ __rest_serializer: "1", fruit, farmers: [farmers[0]] });
|
||||
return response({ __rest_serializer: "1", fruit, farmers, colors });
|
||||
});
|
||||
|
||||
this.get('/fruits', function() {
|
||||
return response({ __rest_serializer: "1", fruits, farmers });
|
||||
return response({ __rest_serializer: "1", fruits, farmers, colors });
|
||||
});
|
||||
|
||||
this.get('/widgets/:widget_id', function(request) {
|
||||
|
|
|
@ -106,19 +106,31 @@ test('destroyRecord when new', function(assert) {
|
|||
});
|
||||
|
||||
|
||||
test('find embedded', function() {
|
||||
test('find embedded', function(assert) {
|
||||
const store = createStore();
|
||||
return store.find('fruit', 1).then(function(f) {
|
||||
ok(f.get('farmer'), 'it has the embedded object');
|
||||
ok(f.get('category'), 'categories are found automatically');
|
||||
return store.find('fruit', 2).then(function(f) {
|
||||
assert.ok(f.get('farmer'), 'it has the embedded object');
|
||||
|
||||
const fruitCols = f.get('colors');
|
||||
assert.equal(fruitCols.length, 2);
|
||||
assert.equal(fruitCols[0].get('id'), 1);
|
||||
assert.equal(fruitCols[1].get('id'), 2);
|
||||
|
||||
assert.ok(f.get('category'), 'categories are found automatically');
|
||||
});
|
||||
});
|
||||
|
||||
test('findAll embedded', function() {
|
||||
test('findAll embedded', function(assert) {
|
||||
const store = createStore();
|
||||
return store.findAll('fruit').then(function(fruits) {
|
||||
equal(fruits.objectAt(0).get('farmer.name'), 'Old MacDonald');
|
||||
equal(fruits.objectAt(0).get('farmer'), fruits.objectAt(1).get('farmer'), 'points at the same object');
|
||||
equal(fruits.objectAt(2).get('farmer.name'), 'Luke Skywalker');
|
||||
assert.equal(fruits.objectAt(0).get('farmer.name'), 'Old MacDonald');
|
||||
assert.equal(fruits.objectAt(0).get('farmer'), fruits.objectAt(1).get('farmer'), 'points at the same object');
|
||||
|
||||
const fruitCols = fruits.objectAt(0).get('colors');
|
||||
assert.equal(fruitCols.length, 2);
|
||||
assert.equal(fruitCols[0].get('id'), 1);
|
||||
assert.equal(fruitCols[1].get('id'), 2);
|
||||
|
||||
assert.equal(fruits.objectAt(2).get('farmer.name'), 'Luke Skywalker');
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue