FEATURE: House Ads
Allows creating ads within Discourse admin at Plugins > House Ads. Write the ads in html and style them with CSS in themes.
This commit is contained in:
parent
5272995e74
commit
1bd80e1afe
|
@ -0,0 +1,10 @@
|
||||||
|
module ::AdPlugin
|
||||||
|
class HouseAdSettingsController < ::ApplicationController
|
||||||
|
requires_plugin AdPlugin.plugin_name
|
||||||
|
|
||||||
|
def update
|
||||||
|
HouseAdSetting.update(params[:id], params[:value])
|
||||||
|
render json: success_json
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,49 @@
|
||||||
|
module ::AdPlugin
|
||||||
|
class HouseAdsController < ::ApplicationController
|
||||||
|
requires_plugin AdPlugin.plugin_name
|
||||||
|
|
||||||
|
def index
|
||||||
|
render_json_dump(
|
||||||
|
house_ads: HouseAd.all.map(&:to_hash),
|
||||||
|
settings: HouseAdSetting.all
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
ad = HouseAd.create(house_ad_params)
|
||||||
|
if ad.valid?
|
||||||
|
render_json_dump(house_ad: ad.to_hash)
|
||||||
|
else
|
||||||
|
render_json_error(ad)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
if ad = HouseAd.find(house_ad_params[:id])
|
||||||
|
ad.update(house_ad_params)
|
||||||
|
else
|
||||||
|
ad = HouseAd.create(house_ad_params.except(:id))
|
||||||
|
end
|
||||||
|
|
||||||
|
if ad.valid?
|
||||||
|
render_json_dump(house_ad: ad.to_hash)
|
||||||
|
else
|
||||||
|
render_json_error(ad)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
if ad = HouseAd.find(house_ad_params[:id])
|
||||||
|
ad.destroy
|
||||||
|
else
|
||||||
|
render_json_error(I18n.t('not_found'), status: 404)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def house_ad_params
|
||||||
|
params.permit(:id, :name, :html)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,100 @@
|
||||||
|
module ::AdPlugin
|
||||||
|
class HouseAd
|
||||||
|
include ActiveModel::Validations
|
||||||
|
|
||||||
|
attr_accessor :id, :name, :html
|
||||||
|
|
||||||
|
NAME_REGEX = /\A[[:alnum:]\s\.,'!@#$%&\*\-\+\=:]*\z/i
|
||||||
|
|
||||||
|
validates :name, presence: true, format: { with: NAME_REGEX }
|
||||||
|
validates :html, presence: true
|
||||||
|
|
||||||
|
validate do
|
||||||
|
if self.class.all.any? { |ad| ad.id != self.id && ad.name.downcase == self.name.downcase }
|
||||||
|
errors.add(:name, :taken) # unique name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
@name = "New Ad"
|
||||||
|
@html = "<div class='house-ad'>New Ad</div>"
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.from_hash(h)
|
||||||
|
ad = self.new
|
||||||
|
ad.name = h[:name]
|
||||||
|
ad.html = h[:html]
|
||||||
|
ad.id = h[:id].to_i if h[:id]
|
||||||
|
ad
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.create(attrs)
|
||||||
|
ad = from_hash(attrs)
|
||||||
|
ad.save
|
||||||
|
ad
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.alloc_id
|
||||||
|
DistributedMutex.synchronize('adplugin-house-ad-id') do
|
||||||
|
max_id = AdPlugin.pstore_get("ad:_id")
|
||||||
|
max_id = 1 unless max_id
|
||||||
|
AdPlugin.pstore_set("ad:_id", max_id + 1)
|
||||||
|
max_id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.find(id)
|
||||||
|
if r = AdPlugin::pstore_get("ad:#{id}")
|
||||||
|
from_hash(r)
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.all
|
||||||
|
PluginStoreRow.where(plugin_name: AdPlugin.plugin_name)
|
||||||
|
.where("key LIKE 'ad:%'")
|
||||||
|
.where("key != 'ad:_id'")
|
||||||
|
.map do |psr|
|
||||||
|
from_hash(PluginStore.cast_value(psr.type_name, psr.value))
|
||||||
|
end.sort_by { |ad| ad.id }
|
||||||
|
end
|
||||||
|
|
||||||
|
def save
|
||||||
|
if self.valid?
|
||||||
|
self.id = self.class.alloc_id if self.id.to_i <= 0
|
||||||
|
AdPlugin::pstore_set("ad:#{id}", to_hash)
|
||||||
|
self.class.publish_if_ads_enabled
|
||||||
|
true
|
||||||
|
else
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def update(attrs)
|
||||||
|
self.name = attrs[:name]
|
||||||
|
self.html = attrs[:html]
|
||||||
|
self.save
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_hash
|
||||||
|
{
|
||||||
|
id: @id,
|
||||||
|
name: @name,
|
||||||
|
html: @html
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
AdPlugin::pstore_delete("ad:#{id}")
|
||||||
|
self.class.publish_if_ads_enabled
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.publish_if_ads_enabled
|
||||||
|
if AdPlugin::HouseAdSetting.all.any? { |_, adsToShow| !adsToShow.blank? }
|
||||||
|
AdPlugin::HouseAdSetting.publish_settings
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,62 @@
|
||||||
|
module ::AdPlugin
|
||||||
|
class HouseAdSetting
|
||||||
|
DEFAULTS = {
|
||||||
|
topic_list_top: '',
|
||||||
|
topic_above_post_stream: '',
|
||||||
|
topic_above_suggested: '',
|
||||||
|
post_bottom: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
def self.all
|
||||||
|
settings = DEFAULTS.dup
|
||||||
|
|
||||||
|
PluginStoreRow.where(plugin_name: AdPlugin.plugin_name)
|
||||||
|
.where("key LIKE 'ad-setting:%'")
|
||||||
|
.each do |psr|
|
||||||
|
settings[psr.key[11..-1].to_sym] = psr.value
|
||||||
|
end
|
||||||
|
|
||||||
|
settings
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.settings_and_ads
|
||||||
|
settings = AdPlugin::HouseAdSetting.all
|
||||||
|
ad_names = settings.values.map { |v| v.split('|') }.flatten.uniq
|
||||||
|
ads = AdPlugin::HouseAd.all.select { |ad| ad_names.include?(ad.name) }
|
||||||
|
{
|
||||||
|
settings: settings.merge(after_nth_post: SiteSetting.house_ads_after_nth_post),
|
||||||
|
creatives: ads.inject({}) { |h, ad| h[ad.name] = ad.html; h }
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.update(setting_name, value)
|
||||||
|
unless DEFAULTS.keys.include?(setting_name.to_sym)
|
||||||
|
raise Discourse::NotFound
|
||||||
|
end
|
||||||
|
|
||||||
|
ad_names = value&.split('|') || []
|
||||||
|
|
||||||
|
if value && ad_names.any? { |v| v !~ HouseAd::NAME_REGEX }
|
||||||
|
raise Discourse::InvalidParameters
|
||||||
|
end
|
||||||
|
|
||||||
|
unless ad_names.empty?
|
||||||
|
ad_names = (HouseAd.all.map(&:name) & ad_names)
|
||||||
|
end
|
||||||
|
|
||||||
|
new_value = ad_names.join('|')
|
||||||
|
|
||||||
|
if value.nil? || new_value == DEFAULTS[setting_name.to_sym]
|
||||||
|
AdPlugin::pstore_delete("ad-setting:#{setting_name}")
|
||||||
|
else
|
||||||
|
AdPlugin::pstore_set("ad-setting:#{setting_name}", new_value)
|
||||||
|
end
|
||||||
|
|
||||||
|
publish_settings
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.publish_settings
|
||||||
|
MessageBus.publish('/site/house-creatives', settings_and_ads)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,10 @@
|
||||||
|
export default {
|
||||||
|
resource: "admin.adminPlugins",
|
||||||
|
path: "/plugins",
|
||||||
|
map() {
|
||||||
|
this.route("houseAds", { path: "/house-ads" }, function() {
|
||||||
|
this.route("index", { path: "/" });
|
||||||
|
this.route("show", { path: "/:ad_id" });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,8 +1,8 @@
|
||||||
import computed from "ember-addons/ember-computed-decorators";
|
import computed from "ember-addons/ember-computed-decorators";
|
||||||
|
|
||||||
export default Ember.Component.extend({
|
export default Ember.Component.extend({
|
||||||
@computed('currentUser.groups')
|
@computed("currentUser.groups")
|
||||||
showToGroups: function(groups) {
|
showToGroups(groups) {
|
||||||
const currentUser = Discourse.User.current();
|
const currentUser = Discourse.User.current();
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -17,5 +17,13 @@ export default Ember.Component.extend({
|
||||||
const noAdsGroupNames = this.siteSettings.no_ads_for_groups.split("|");
|
const noAdsGroupNames = this.siteSettings.no_ads_for_groups.split("|");
|
||||||
|
|
||||||
return !groups.any(group => noAdsGroupNames.includes(group.name));
|
return !groups.any(group => noAdsGroupNames.includes(group.name));
|
||||||
|
},
|
||||||
|
|
||||||
|
isNthPost(n) {
|
||||||
|
if (n && n > 0) {
|
||||||
|
return this.get("postNumber") % n === 0;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
|
@ -0,0 +1,84 @@
|
||||||
|
import computed from "ember-addons/ember-computed-decorators";
|
||||||
|
|
||||||
|
const adConfig = Ember.Object.create({
|
||||||
|
"google-adsense": {
|
||||||
|
settingPrefix: "adsense" // settings follow naming convention
|
||||||
|
},
|
||||||
|
"google-dfp-ad": {
|
||||||
|
settingPrefix: "dfp" // settings follow naming convention
|
||||||
|
},
|
||||||
|
"amazon-product-links": {
|
||||||
|
settingPrefix: "amazon",
|
||||||
|
desktop: {
|
||||||
|
"topic-list-top": "amazon_topic_list_top_src_code",
|
||||||
|
"post-bottom": "amazon_post_bottom_src_code",
|
||||||
|
"topic-above-post-stream": "amazon_topic_above_post_stream_src_code",
|
||||||
|
"topic-above-suggested": "amazon_topic_above_suggested_src_code"
|
||||||
|
},
|
||||||
|
mobile: {
|
||||||
|
"topic-list-top": "amazon_mobile_topic_list_top_src_code",
|
||||||
|
"post-bottom": "amazon_mobile_post_bottom_src_code",
|
||||||
|
"topic-above-post-stream":
|
||||||
|
"amazon_mobile_topic_above_post_stream_src_code",
|
||||||
|
"topic-above-suggested": "amazon_mobile_topic_above_suggested_src_code"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"codefund-ad": {
|
||||||
|
settingPrefix: "codefund",
|
||||||
|
desktop: {
|
||||||
|
"topic-list-top": "codefund_top_of_topic_list_enabled",
|
||||||
|
"post-bottom": "codefund_below_post_enabled",
|
||||||
|
"topic-above-post-stream": "codefund_above_post_stream_enabled",
|
||||||
|
"topic-above-suggested": "codefund_above_suggested_enabled"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"carbonads-ad": {
|
||||||
|
settingPrefix: "carbonads",
|
||||||
|
desktop: {
|
||||||
|
"topic-list-top": "carbonads_topic_list_top_enabled",
|
||||||
|
"post-bottom": false,
|
||||||
|
"topic-above-post-stream": "carbonads_above_post_stream_enabled",
|
||||||
|
"topic-above-suggested": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
@computed("placement")
|
||||||
|
adComponents(placement) {
|
||||||
|
// Check house ads first
|
||||||
|
const houseAds = this.site.get("house_creatives"),
|
||||||
|
adsForSlot = houseAds.settings[placement.replace(/-/g, "_")];
|
||||||
|
if (
|
||||||
|
Object.keys(houseAds.creatives).length > 0 &&
|
||||||
|
!Ember.isBlank(adsForSlot)
|
||||||
|
) {
|
||||||
|
return ["house-ad"];
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.keys(adConfig).filter(adNetwork => {
|
||||||
|
const config = adConfig[adNetwork];
|
||||||
|
let settingNames = null,
|
||||||
|
name;
|
||||||
|
|
||||||
|
if (this.site.mobileView) {
|
||||||
|
settingNames = config.mobile || config.desktop;
|
||||||
|
} else {
|
||||||
|
settingNames = config.desktop;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settingNames) {
|
||||||
|
name = settingNames[placement];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name === undefined) {
|
||||||
|
// follows naming convention: prefix_(mobile_)_{placement}_code
|
||||||
|
name = `${config.settingPrefix}_${
|
||||||
|
this.site.mobileView ? "mobile_" : ""
|
||||||
|
}${placement.replace(/-/g, "_")}_code`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return name !== false && !Ember.isBlank(this.siteSettings[name]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
|
@ -1,4 +1,4 @@
|
||||||
import AdComponent from "discourse/plugins/discourse-adplugin/discourse/components/ad_component";
|
import AdComponent from "discourse/plugins/discourse-adplugin/discourse/components/ad-component";
|
||||||
import computed from "ember-addons/ember-computed-decorators";
|
import computed from "ember-addons/ember-computed-decorators";
|
||||||
|
|
||||||
const currentUser = Discourse.User.current();
|
const currentUser = Discourse.User.current();
|
||||||
|
@ -125,7 +125,11 @@ if (
|
||||||
export default AdComponent.extend({
|
export default AdComponent.extend({
|
||||||
classNames: ["amazon-product-links"],
|
classNames: ["amazon-product-links"],
|
||||||
|
|
||||||
showAd: Ember.computed.and("showToTrustLevel", "showToGroups"),
|
showAd: Ember.computed.and(
|
||||||
|
"showToTrustLevel",
|
||||||
|
"showToGroups",
|
||||||
|
"showAfterPost"
|
||||||
|
),
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
let placement = this.get("placement");
|
let placement = this.get("placement");
|
||||||
|
@ -169,5 +173,14 @@ export default AdComponent.extend({
|
||||||
trustLevel &&
|
trustLevel &&
|
||||||
trustLevel > Discourse.SiteSettings.amazon_through_trust_level
|
trustLevel > Discourse.SiteSettings.amazon_through_trust_level
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
@computed("postNumber")
|
||||||
|
showAfterPost(postNumber) {
|
||||||
|
if (!postNumber) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.isNthPost(parseInt(this.siteSettings.amazon_nth_post_code));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import AdComponent from "discourse/plugins/discourse-adplugin/discourse/components/ad_component";
|
import AdComponent from "discourse/plugins/discourse-adplugin/discourse/components/ad-component";
|
||||||
import {
|
import {
|
||||||
default as computed,
|
default as computed,
|
||||||
observes
|
observes
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import AdComponent from "discourse/plugins/discourse-adplugin/discourse/components/ad_component";
|
import AdComponent from "discourse/plugins/discourse-adplugin/discourse/components/ad-component";
|
||||||
import {
|
import {
|
||||||
default as computed,
|
default as computed,
|
||||||
observes
|
observes
|
||||||
|
@ -108,17 +108,26 @@ export default AdComponent.extend({
|
||||||
@computed("currentUser.trust_level")
|
@computed("currentUser.trust_level")
|
||||||
showToTrustLevel(trustLevel) {
|
showToTrustLevel(trustLevel) {
|
||||||
return !(
|
return !(
|
||||||
trustLevel &&
|
trustLevel && trustLevel > this.siteSettings.codefund_through_trust_level
|
||||||
trustLevel > Discourse.SiteSettings.codefund_through_trust_level
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
@computed("showToTrustLevel", "showToGroups")
|
@computed("showToTrustLevel", "showToGroups", "showAfterPost")
|
||||||
showAd(showToTrustLevel, showToGroups) {
|
showAd(showToTrustLevel, showToGroups, showAfterPost) {
|
||||||
return (
|
return (
|
||||||
Discourse.SiteSettings.codefund_property_id &&
|
this.siteSettings.codefund_property_id &&
|
||||||
showToTrustLevel &&
|
showToTrustLevel &&
|
||||||
showToGroups
|
showToGroups &&
|
||||||
|
showAfterPost
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
@computed("postNumber")
|
||||||
|
showAfterPost(postNumber) {
|
||||||
|
if (!postNumber) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.isNthPost(parseInt(this.siteSettings.codefund_nth_post));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import AdComponent from "discourse/plugins/discourse-adplugin/discourse/components/ad_component";
|
import AdComponent from "discourse/plugins/discourse-adplugin/discourse/components/ad-component";
|
||||||
import {
|
import {
|
||||||
default as computed,
|
default as computed,
|
||||||
observes
|
observes
|
||||||
|
@ -233,17 +233,26 @@ export default AdComponent.extend({
|
||||||
@computed("currentUser.trust_level")
|
@computed("currentUser.trust_level")
|
||||||
showToTrustLevel(trustLevel) {
|
showToTrustLevel(trustLevel) {
|
||||||
return !(
|
return !(
|
||||||
trustLevel &&
|
trustLevel && trustLevel > this.siteSettings.adsense_through_trust_level
|
||||||
trustLevel > Discourse.SiteSettings.adsense_through_trust_level
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
@computed("showToTrustLevel", "showToGroups")
|
@computed("showToTrustLevel", "showToGroups", "showAfterPost")
|
||||||
showAd(showToTrustLevel, showToGroups) {
|
showAd(showToTrustLevel, showToGroups, showAfterPost) {
|
||||||
return (
|
return (
|
||||||
|
this.siteSettings.adsense_publisher_code &&
|
||||||
showToTrustLevel &&
|
showToTrustLevel &&
|
||||||
showToGroups &&
|
showToGroups &&
|
||||||
Discourse.SiteSettings.adsense_publisher_code
|
showAfterPost
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
@computed("postNumber")
|
||||||
|
showAfterPost(postNumber) {
|
||||||
|
if (!postNumber) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.isNthPost(parseInt(this.siteSettings.adsense_nth_post_code));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import AdComponent from "discourse/plugins/discourse-adplugin/discourse/components/ad_component";
|
import AdComponent from "discourse/plugins/discourse-adplugin/discourse/components/ad-component";
|
||||||
import {
|
import {
|
||||||
default as computed,
|
default as computed,
|
||||||
observes,
|
observes,
|
||||||
|
@ -234,23 +234,32 @@ export default AdComponent.extend({
|
||||||
return `width: ${w}px;`.htmlSafe();
|
return `width: ${w}px;`.htmlSafe();
|
||||||
},
|
},
|
||||||
|
|
||||||
@computed("showToTrustLevel", "showToGroups")
|
@computed("showToTrustLevel", "showToGroups", "showAfterPost")
|
||||||
showAd(showToTrustLevel, showToGroups) {
|
showAd(showToTrustLevel, showToGroups, showAfterPost) {
|
||||||
return (
|
return (
|
||||||
Discourse.SiteSettings.dfp_publisher_id &&
|
this.siteSettings.dfp_publisher_id &&
|
||||||
showToTrustLevel &&
|
showToTrustLevel &&
|
||||||
showToGroups
|
showToGroups &&
|
||||||
|
showAfterPost
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
@computed("currentUser.trust_level")
|
@computed("currentUser.trust_level")
|
||||||
showToTrustLevel(trustLevel) {
|
showToTrustLevel(trustLevel) {
|
||||||
return !(
|
return !(
|
||||||
trustLevel &&
|
trustLevel && trustLevel > this.siteSettings.dfp_through_trust_level
|
||||||
trustLevel > Discourse.SiteSettings.dfp_through_trust_level
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@computed("postNumber")
|
||||||
|
showAfterPost(postNumber) {
|
||||||
|
if (!postNumber) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.isNthPost(parseInt(this.siteSettings.dfp_nth_post_code));
|
||||||
|
},
|
||||||
|
|
||||||
@observes("refreshOnChange")
|
@observes("refreshOnChange")
|
||||||
refreshAd() {
|
refreshAd() {
|
||||||
var slot = ads[this.get("divId")];
|
var slot = ads[this.get("divId")];
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
import AdComponent from "discourse/plugins/discourse-adplugin/discourse/components/ad-component";
|
||||||
|
import {
|
||||||
|
default as computed,
|
||||||
|
observes,
|
||||||
|
on
|
||||||
|
} from "ember-addons/ember-computed-decorators";
|
||||||
|
|
||||||
|
const adIndex = {
|
||||||
|
topic_list_top: null,
|
||||||
|
topic_above_post_stream: null,
|
||||||
|
topic_above_suggested: null,
|
||||||
|
post_bottom: null
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AdComponent.extend({
|
||||||
|
classNames: ["house-creative"],
|
||||||
|
classNameBindings: ["adUnitClass"],
|
||||||
|
adHtml: "",
|
||||||
|
|
||||||
|
@computed("placement", "showAd")
|
||||||
|
adUnitClass(placement, showAd) {
|
||||||
|
return showAd ? `house-${placement}` : "";
|
||||||
|
},
|
||||||
|
|
||||||
|
@computed("showToGroups", "showAfterPost")
|
||||||
|
showAd(showToGroups, showAfterPost) {
|
||||||
|
return showToGroups && showAfterPost;
|
||||||
|
},
|
||||||
|
|
||||||
|
@computed("postNumber")
|
||||||
|
showAfterPost(postNumber) {
|
||||||
|
if (!postNumber) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.isNthPost(
|
||||||
|
parseInt(this.site.get("house_creatives.settings.after_nth_post"))
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
chooseAdHtml() {
|
||||||
|
const houseAds = this.site.get("house_creatives"),
|
||||||
|
placement = this.get("placement").replace(/-/g, "_"),
|
||||||
|
adNames = this.adsNamesForSlot(placement);
|
||||||
|
|
||||||
|
if (adNames.length > 0) {
|
||||||
|
if (!adIndex[placement]) {
|
||||||
|
adIndex[placement] = 0;
|
||||||
|
}
|
||||||
|
let ad = houseAds.creatives[adNames[adIndex[placement]]] || "";
|
||||||
|
adIndex[placement] = (adIndex[placement] + 1) % adNames.length;
|
||||||
|
return ad;
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
adsNamesForSlot(placement) {
|
||||||
|
const houseAds = this.site.get("house_creatives"),
|
||||||
|
adsForSlot = houseAds.settings[placement];
|
||||||
|
|
||||||
|
if (
|
||||||
|
Object.keys(houseAds.creatives).length > 0 &&
|
||||||
|
!Ember.isBlank(adsForSlot)
|
||||||
|
) {
|
||||||
|
return adsForSlot.split("|");
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
@observes("refreshOnChange")
|
||||||
|
refreshAd() {
|
||||||
|
if (this.get("listLoading")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.set("adHtml", this.chooseAdHtml());
|
||||||
|
},
|
||||||
|
|
||||||
|
didInsertElement() {
|
||||||
|
this._super(...arguments);
|
||||||
|
|
||||||
|
if (!this.get("showAd")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.get("listLoading")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!adIndex["topic_list_top"]) {
|
||||||
|
// start at a random spot in the ad inventory
|
||||||
|
Object.keys(adIndex).forEach(placement => {
|
||||||
|
const adNames = this.adsNamesForSlot(placement);
|
||||||
|
adIndex[placement] = Math.floor(Math.random() * adNames.length);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.refreshAd();
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,29 @@
|
||||||
|
import MultiSelectComponent from "select-kit/components/multi-select";
|
||||||
|
import computed from "ember-addons/ember-computed-decorators";
|
||||||
|
const { makeArray } = Ember;
|
||||||
|
|
||||||
|
export default MultiSelectComponent.extend({
|
||||||
|
classNames: "house-ads-chooser",
|
||||||
|
filterable: true,
|
||||||
|
filterPlaceholder: "admin.adplugin.house_ads.filter_placeholder",
|
||||||
|
tokenSeparator: "|",
|
||||||
|
allowCreate: false,
|
||||||
|
allowAny: false,
|
||||||
|
settingValue: "",
|
||||||
|
|
||||||
|
computeContent() {
|
||||||
|
return makeArray(this.get("choices"));
|
||||||
|
},
|
||||||
|
|
||||||
|
// called after a selection is made
|
||||||
|
mutateValues(values) {
|
||||||
|
this.set("settingValue", values.join(this.get("tokenSeparator")));
|
||||||
|
},
|
||||||
|
|
||||||
|
// called when first rendered
|
||||||
|
computeValues() {
|
||||||
|
return this.get("settingValue")
|
||||||
|
.split(this.get("tokenSeparator"))
|
||||||
|
.filter(c => c);
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { ajax } from "discourse/lib/ajax";
|
||||||
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||||
|
import computed from "ember-addons/ember-computed-decorators";
|
||||||
|
import HouseAdsSetting from "discourse/plugins/discourse-adplugin/discourse/components/house-ads-setting";
|
||||||
|
|
||||||
|
export default HouseAdsSetting.extend({
|
||||||
|
classNames: "house-ads-setting house-ads-list-setting",
|
||||||
|
adNames: Ember.computed.mapBy("allAds", "name")
|
||||||
|
});
|
|
@ -0,0 +1,57 @@
|
||||||
|
import { ajax } from "discourse/lib/ajax";
|
||||||
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||||
|
import { i18n, propertyNotEqual } from "discourse/lib/computed";
|
||||||
|
import computed from "ember-addons/ember-computed-decorators";
|
||||||
|
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
classNames: "house-ads-setting",
|
||||||
|
adValue: "",
|
||||||
|
saving: false,
|
||||||
|
savingStatus: "",
|
||||||
|
title: i18n("name", "admin.adplugin.house_ads.%@.title"),
|
||||||
|
help: i18n("name", "admin.adplugin.house_ads.%@.description"),
|
||||||
|
changed: propertyNotEqual("adValue", "value"),
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this._super(...arguments);
|
||||||
|
this.set("adValue", this.get("value"));
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
save() {
|
||||||
|
if (!this.get("saving")) {
|
||||||
|
this.setProperties({
|
||||||
|
saving: true,
|
||||||
|
savingStatus: I18n.t("saving")
|
||||||
|
});
|
||||||
|
|
||||||
|
ajax(
|
||||||
|
`/admin/plugins/adplugin/house_ad_settings/${this.get("name")}.json`,
|
||||||
|
{
|
||||||
|
type: "PUT",
|
||||||
|
data: { value: this.get("adValue") }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then(data => {
|
||||||
|
const adSettings = this.get("adSettings");
|
||||||
|
adSettings.set(this.get("name"), this.get("adValue"));
|
||||||
|
this.setProperties({
|
||||||
|
value: this.get("adValue"),
|
||||||
|
savingStatus: I18n.t("saved")
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(popupAjaxError)
|
||||||
|
.finally(() => {
|
||||||
|
this.setProperties({
|
||||||
|
saving: false,
|
||||||
|
savingStatus: ""
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
cancel() {
|
||||||
|
this.set("adValue", this.get("value"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,5 @@
|
||||||
|
export default Ember.Controller.extend({
|
||||||
|
adminPluginsHouseAds: Ember.inject.controller("adminPlugins.houseAds"),
|
||||||
|
houseAds: Ember.computed.alias("adminPluginsHouseAds.model"),
|
||||||
|
adSettings: Ember.computed.alias("adminPluginsHouseAds.houseAdsSettings")
|
||||||
|
});
|
|
@ -0,0 +1,93 @@
|
||||||
|
import { ajax } from "discourse/lib/ajax";
|
||||||
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||||
|
import { propertyNotEqual } from "discourse/lib/computed";
|
||||||
|
import { bufferedProperty } from "discourse/mixins/buffered-content";
|
||||||
|
|
||||||
|
export default Ember.Controller.extend(bufferedProperty("model"), {
|
||||||
|
adminPluginsHouseAds: Ember.inject.controller("adminPlugins.houseAds"),
|
||||||
|
|
||||||
|
saving: false,
|
||||||
|
savingStatus: "",
|
||||||
|
|
||||||
|
nameDirty: propertyNotEqual("buffered.name", "model.name"),
|
||||||
|
htmlDirty: propertyNotEqual("buffered.html", "model.html"),
|
||||||
|
dirty: Ember.computed.or("nameDirty", "htmlDirty"),
|
||||||
|
disableSave: Ember.computed.not("dirty"),
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
save() {
|
||||||
|
if (!this.get("saving")) {
|
||||||
|
this.setProperties({
|
||||||
|
saving: true,
|
||||||
|
savingStatus: I18n.t("saving")
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = {},
|
||||||
|
buffered = this.get("buffered"),
|
||||||
|
newRecord = !buffered.get("id");
|
||||||
|
|
||||||
|
if (!newRecord) {
|
||||||
|
data.id = buffered.get("id");
|
||||||
|
}
|
||||||
|
data.name = buffered.get("name");
|
||||||
|
data.html = buffered.get("html");
|
||||||
|
|
||||||
|
ajax(
|
||||||
|
newRecord
|
||||||
|
? `/admin/plugins/adplugin/house_ads`
|
||||||
|
: `/admin/plugins/adplugin/house_ads/${buffered.get("id")}`,
|
||||||
|
{
|
||||||
|
type: newRecord ? "POST" : "PUT",
|
||||||
|
data
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then(data => {
|
||||||
|
this.commitBuffer();
|
||||||
|
this.set("savingStatus", I18n.t("saved"));
|
||||||
|
if (newRecord) {
|
||||||
|
const model = this.get("model");
|
||||||
|
model.set("id", data.house_ad.id);
|
||||||
|
const houseAds = this.get("adminPluginsHouseAds.model");
|
||||||
|
if (!houseAds.includes(model)) {
|
||||||
|
houseAds.pushObject(model);
|
||||||
|
}
|
||||||
|
this.transitionToRoute(
|
||||||
|
"adminPlugins.houseAds.show",
|
||||||
|
model.get("id")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(popupAjaxError)
|
||||||
|
.finally(() => {
|
||||||
|
this.setProperties({
|
||||||
|
saving: false,
|
||||||
|
savingStatus: ""
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
cancel() {
|
||||||
|
this.rollbackBuffer();
|
||||||
|
},
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
const houseAds = this.get("adminPluginsHouseAds.model");
|
||||||
|
const model = this.get("model");
|
||||||
|
|
||||||
|
if (!model.get("id")) {
|
||||||
|
this.transitionToRoute("adminPlugins.houseAds.index");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ajax(`/admin/plugins/adplugin/house_ads/${model.get("id")}`, {
|
||||||
|
type: "DELETE"
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
houseAds.removeObject(model);
|
||||||
|
this.transitionToRoute("adminPlugins.houseAds.index");
|
||||||
|
})
|
||||||
|
.catch(() => bootbox.alert(I18n.t("generic_error")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,3 @@
|
||||||
|
export default Ember.Controller.extend({
|
||||||
|
loadingAds: true
|
||||||
|
});
|
|
@ -0,0 +1,7 @@
|
||||||
|
export default Discourse.Route.extend({
|
||||||
|
actions: {
|
||||||
|
moreSettings() {
|
||||||
|
this.transitionTo("adminSiteSettingsCategory", "ad_plugin");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,15 @@
|
||||||
|
export default Discourse.Route.extend({
|
||||||
|
model(params) {
|
||||||
|
if (params.ad_id === "new") {
|
||||||
|
return Ember.Object.create({
|
||||||
|
name: I18n.t("admin.adplugin.house_ads.new_name"),
|
||||||
|
html: ""
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return this.modelFor("adminPlugins.houseAds").findBy(
|
||||||
|
"id",
|
||||||
|
parseInt(params.ad_id, 10)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { ajax } from "discourse/lib/ajax";
|
||||||
|
|
||||||
|
export default Discourse.Route.extend({
|
||||||
|
settings: null,
|
||||||
|
|
||||||
|
model(params) {
|
||||||
|
return ajax("/admin/plugins/adplugin/house_ads.json").then(data => {
|
||||||
|
this.set("settings", Ember.Object.create(data.settings));
|
||||||
|
return data.house_ads.map(ad => Ember.Object.create(ad));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
setupController(controller, model) {
|
||||||
|
controller.setProperties({
|
||||||
|
model,
|
||||||
|
houseAdsSettings: this.get("settings"),
|
||||||
|
loadingAds: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,23 @@
|
||||||
|
{{#d-section class="house-ads-settings content-body"}}
|
||||||
|
<div>{{i18n 'admin.adplugin.house_ads.description'}}</div>
|
||||||
|
|
||||||
|
{{#unless houseAds.length}}
|
||||||
|
<p>
|
||||||
|
{{#link-to 'adminPlugins.houseAds.show' 'new'}}
|
||||||
|
{{i18n 'admin.adplugin.house_ads.get_started'}}
|
||||||
|
{{/link-to}}
|
||||||
|
</p>
|
||||||
|
{{else}}
|
||||||
|
<form class="form-horizontal">
|
||||||
|
{{house-ads-list-setting name="topic_list_top" value=adSettings.topic_list_top allAds=houseAds adSettings=adSettings}}
|
||||||
|
{{house-ads-list-setting name="topic_above_post_stream" value=adSettings.topic_above_post_stream allAds=houseAds adSettings=adSettings}}
|
||||||
|
{{house-ads-list-setting name="topic_above_suggested" value=adSettings.topic_above_suggested allAds=houseAds adSettings=adSettings}}
|
||||||
|
{{house-ads-list-setting name="post_bottom" value=adSettings.post_bottom allAds=houseAds adSettings=adSettings}}
|
||||||
|
|
||||||
|
{{d-button label="admin.adplugin.house_ads.more_settings"
|
||||||
|
icon="cog"
|
||||||
|
class="btn-default"
|
||||||
|
action=(route-action "moreSettings")}}
|
||||||
|
</form>
|
||||||
|
{{/unless}}
|
||||||
|
{{/d-section}}
|
|
@ -0,0 +1,26 @@
|
||||||
|
{{#d-section class="edit-house-ad content-body"}}
|
||||||
|
<h1>{{text-field class="house-ad-name" value=buffered.name}}</h1>
|
||||||
|
<div class="controls">
|
||||||
|
{{ace-editor content=buffered.html mode="html"}}
|
||||||
|
</div>
|
||||||
|
<div class="controls">
|
||||||
|
{{d-button
|
||||||
|
action=(action "save")
|
||||||
|
disabled=disableSave
|
||||||
|
class="btn-primary"
|
||||||
|
label="admin.adplugin.house_ads.save"}}
|
||||||
|
|
||||||
|
{{#if saving}}
|
||||||
|
{{savingStatus}}
|
||||||
|
{{else}}
|
||||||
|
{{#if dirty}}
|
||||||
|
<a href {{action "cancel"}}>{{i18n 'cancel'}}</a>
|
||||||
|
{{/if}}
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{d-button
|
||||||
|
action=(action "destroy")
|
||||||
|
class="btn-danger delete-button"
|
||||||
|
label="admin.adplugin.house_ads.delete"}}
|
||||||
|
</div>
|
||||||
|
{{/d-section}}
|
|
@ -0,0 +1,27 @@
|
||||||
|
<div class="adplugin-mgmt">
|
||||||
|
<h1>{{i18n 'admin.adplugin.house_ads.title'}}</h1>
|
||||||
|
{{#if model.length}}
|
||||||
|
<div class="content-list">
|
||||||
|
<div class="house-ads-actions">
|
||||||
|
{{#link-to 'adminPlugins.houseAds.show' 'new' class="btn btn-primary"}}
|
||||||
|
{{d-icon "plus"}}
|
||||||
|
<span>{{i18n 'admin.adplugin.house_ads.new'}}</span>
|
||||||
|
{{/link-to}}
|
||||||
|
{{#link-to 'adminPlugins.houseAds.index' class="btn btn-default"}}
|
||||||
|
{{d-icon "cog"}}
|
||||||
|
<span>{{i18n 'admin.adplugin.house_ads.settings'}}</span>
|
||||||
|
{{/link-to}}
|
||||||
|
</div>
|
||||||
|
<ul class="house-ads-list">
|
||||||
|
{{#each model as |ad|}}
|
||||||
|
<li class="house-ads-list-item">
|
||||||
|
{{#link-to 'adminPlugins.houseAds.show' ad.id}}
|
||||||
|
{{ad.name}}
|
||||||
|
{{/link-to}}
|
||||||
|
</li>
|
||||||
|
{{/each}}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
{{outlet}}
|
||||||
|
</div>
|
|
@ -0,0 +1,8 @@
|
||||||
|
{{#each adComponents as |adComponent|}}
|
||||||
|
{{component adComponent
|
||||||
|
placement=placement
|
||||||
|
refreshOnChange=refreshOnChange
|
||||||
|
category=category
|
||||||
|
listLoading=listLoading
|
||||||
|
postNumber=postNumber}}
|
||||||
|
{{/each}}
|
|
@ -1,43 +0,0 @@
|
||||||
{{#if site.mobileView}}
|
|
||||||
{{#if model.postSpecificCountAdsense}}
|
|
||||||
{{#if siteSettings.adsense_mobile_post_bottom_code}}
|
|
||||||
{{google-adsense placement="post-bottom" postNumber=model.post_number}}
|
|
||||||
{{/if}}
|
|
||||||
{{/if}}
|
|
||||||
{{#if model.postSpecificCountDFP}}
|
|
||||||
{{#if siteSettings.dfp_mobile_post_bottom_code}}
|
|
||||||
{{google-dfp-ad placement="post-bottom" category=model.topic.category.slug postNumber=model.post_number}}
|
|
||||||
{{/if}}
|
|
||||||
{{/if}}
|
|
||||||
{{#if model.postSpecificCountAmazon}}
|
|
||||||
{{#if siteSettings.amazon_mobile_post_bottom_src_code}}
|
|
||||||
{{amazon-product-links placement="post-bottom" postNumber=model.post_number}}
|
|
||||||
{{/if}}
|
|
||||||
{{/if}}
|
|
||||||
{{#if model.postSpecificCountCodeFund}}
|
|
||||||
{{#if siteSettings.codefund_below_post_enabled}}
|
|
||||||
{{codefund-ad placement="post-bottom" postNumber=model.post_number}}
|
|
||||||
{{/if}}
|
|
||||||
{{/if}}
|
|
||||||
{{else}}
|
|
||||||
{{#if model.postSpecificCountAdsense}}
|
|
||||||
{{#if siteSettings.adsense_post_bottom_code}}
|
|
||||||
{{google-adsense placement="post-bottom" postNumber=model.post_number}}
|
|
||||||
{{/if}}
|
|
||||||
{{/if}}
|
|
||||||
{{#if model.postSpecificCountDFP}}
|
|
||||||
{{#if siteSettings.dfp_post_bottom_code}}
|
|
||||||
{{google-dfp-ad placement="post-bottom" category=model.topic.category.slug postNumber=model.post_number}}
|
|
||||||
{{/if}}
|
|
||||||
{{/if}}
|
|
||||||
{{#if model.postSpecificCountAmazon}}
|
|
||||||
{{#if siteSettings.amazon_post_bottom_src_code}}
|
|
||||||
{{amazon-product-links placement="post-bottom" postNumber=model.post_number}}
|
|
||||||
{{/if}}
|
|
||||||
{{/if}}
|
|
||||||
{{#if model.postSpecificCountCodeFund}}
|
|
||||||
{{#if siteSettings.codefund_below_post_enabled}}
|
|
||||||
{{codefund-ad placement="post-bottom" postNumber=model.post_number}}
|
|
||||||
{{/if}}
|
|
||||||
{{/if}}
|
|
||||||
{{/if}}
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
{{#if showAd}}
|
||||||
|
{{{adHtml}}}
|
||||||
|
{{/if}}
|
|
@ -0,0 +1,9 @@
|
||||||
|
<label for="{{name}}">{{title}}</label>
|
||||||
|
{{house-ads-chooser settingValue=adValue choices=adNames}}
|
||||||
|
<div class='setting-controls'>
|
||||||
|
{{#if changed}}
|
||||||
|
{{d-button class="ok" action=(action "save") icon="check"}}
|
||||||
|
{{d-button class="cancel" action=(action "cancel") icon="times"}}
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
<p class='help'>{{help}}</p>
|
|
@ -0,0 +1,9 @@
|
||||||
|
<label for="{{name}}">{{title}}</label>
|
||||||
|
{{text-field value=adValue classNames="house-ads-text-input"}}
|
||||||
|
<div class='setting-controls'>
|
||||||
|
{{#if changed}}
|
||||||
|
{{d-button class="ok" action=(action "save") icon="check"}}
|
||||||
|
{{d-button class="cancel" action=(action "cancel") icon="times"}}
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
<p class='help'>{{help}}</p>
|
|
@ -0,0 +1 @@
|
||||||
|
{{ad-slot placement="post-bottom" category=model.topic.category.slug postNumber=model.post_number}}
|
|
@ -1,33 +1 @@
|
||||||
{{#if site.mobileView}}
|
{{ad-slot placement="topic-list-top" refreshOnChange=listLoading category=category.slug listLoading=listLoading}}
|
||||||
{{#if siteSettings.adsense_mobile_topic_list_top_code}}
|
|
||||||
{{google-adsense placement="topic-list-top" listLoading=listLoading}}
|
|
||||||
{{/if}}
|
|
||||||
{{#if siteSettings.dfp_mobile_topic_list_top_code}}
|
|
||||||
{{google-dfp-ad placement="topic-list-top" refreshOnChange=listLoading category=category.slug listLoading=listLoading}}
|
|
||||||
{{/if}}
|
|
||||||
{{#if siteSettings.amazon_mobile_topic_list_top_src_code}}
|
|
||||||
{{amazon-product-links placement="topic-list-top" listLoading=listLoading}}
|
|
||||||
{{/if}}
|
|
||||||
{{#if siteSettings.codefund_top_of_topic_list_enabled}}
|
|
||||||
{{codefund-ad placement="topic-list-top" listLoading=listLoading refreshOnChange=listLoading}}
|
|
||||||
{{/if}}
|
|
||||||
{{#if siteSettings.carbonads_topic_list_top_enabled}}
|
|
||||||
{{carbonads-ad}}
|
|
||||||
{{/if}}
|
|
||||||
{{else}}
|
|
||||||
{{#if siteSettings.adsense_topic_list_top_code}}
|
|
||||||
{{google-adsense placement="topic-list-top" listLoading=listLoading}}
|
|
||||||
{{/if}}
|
|
||||||
{{#if siteSettings.dfp_topic_list_top_code}}
|
|
||||||
{{google-dfp-ad placement="topic-list-top" refreshOnChange=listLoading category=category.slug listLoading=listLoading}}
|
|
||||||
{{/if}}
|
|
||||||
{{#if siteSettings.amazon_topic_list_top_src_code}}
|
|
||||||
{{amazon-product-links placement="topic-list-top" listLoading=listLoading}}
|
|
||||||
{{/if}}
|
|
||||||
{{#if siteSettings.codefund_top_of_topic_list_enabled}}
|
|
||||||
{{codefund-ad placement="topic-list-top" listLoading=listLoading refreshOnChange=listLoading}}
|
|
||||||
{{/if}}
|
|
||||||
{{#if siteSettings.carbonads_topic_list_top_enabled}}
|
|
||||||
{{carbonads-ad}}
|
|
||||||
{{/if}}
|
|
||||||
{{/if}}
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
{{adplugin-container model=this}}
|
{{post-bottom-ad model=this}}
|
||||||
|
|
|
@ -1,33 +1 @@
|
||||||
{{#if site.mobileView}}
|
{{ad-slot placement="topic-above-post-stream" refreshOnChange=model.id category=model.category.slug}}
|
||||||
{{#if siteSettings.adsense_mobile_topic_above_post_stream_code}}
|
|
||||||
{{google-adsense placement="topic-above-post-stream"}}
|
|
||||||
{{/if}}
|
|
||||||
{{#if siteSettings.dfp_mobile_topic_above_post_stream_code}}
|
|
||||||
{{google-dfp-ad placement="topic-above-post-stream" refreshOnChange=model.id category=model.category.slug}}
|
|
||||||
{{/if}}
|
|
||||||
{{#if siteSettings.amazon_mobile_topic_above_post_stream_src_code}}
|
|
||||||
{{amazon-product-links placement="topic-above-post-stream"}}
|
|
||||||
{{/if}}
|
|
||||||
{{#if siteSettings.codefund_above_post_stream_enabled}}
|
|
||||||
{{codefund-ad placement="topic-above-post-stream"}}
|
|
||||||
{{/if}}
|
|
||||||
{{#if siteSettings.carbonads_above_post_stream_enabled}}
|
|
||||||
{{carbonads-ad}}
|
|
||||||
{{/if}}
|
|
||||||
{{else}}
|
|
||||||
{{#if siteSettings.adsense_topic_above_post_stream_code}}
|
|
||||||
{{google-adsense placement="topic-above-post-stream"}}
|
|
||||||
{{/if}}
|
|
||||||
{{#if siteSettings.dfp_topic_above_post_stream_code}}
|
|
||||||
{{google-dfp-ad placement="topic-above-post-stream" refreshOnChange=model.id category=model.category.slug}}
|
|
||||||
{{/if}}
|
|
||||||
{{#if siteSettings.amazon_topic_above_post_stream_src_code}}
|
|
||||||
{{amazon-product-links placement="topic-above-post-stream"}}
|
|
||||||
{{/if}}
|
|
||||||
{{#if siteSettings.codefund_above_post_stream_enabled}}
|
|
||||||
{{codefund-ad placement="topic-above-post-stream"}}
|
|
||||||
{{/if}}
|
|
||||||
{{#if siteSettings.carbonads_above_post_stream_enabled}}
|
|
||||||
{{carbonads-ad}}
|
|
||||||
{{/if}}
|
|
||||||
{{/if}}
|
|
||||||
|
|
|
@ -1,27 +1 @@
|
||||||
{{#if site.mobileView}}
|
{{ad-slot placement="topic-above-suggested" refreshOnChange=model.id category=model.category.slug}}
|
||||||
{{#if siteSettings.adsense_mobile_topic_above_suggested_code}}
|
|
||||||
{{google-adsense placement="topic-above-suggested"}}
|
|
||||||
{{/if}}
|
|
||||||
{{#if siteSettings.dfp_mobile_topic_above_suggested_code}}
|
|
||||||
{{google-dfp-ad placement="topic-above-suggested" refreshOnChange=model.id category=model.category.slug}}
|
|
||||||
{{/if}}
|
|
||||||
{{#if siteSettings.amazon_mobile_topic_above_suggested_src_code}}
|
|
||||||
{{amazon-product-links placement="topic-above-suggested"}}
|
|
||||||
{{/if}}
|
|
||||||
{{#if siteSettings.codefund_above_suggested_enabled}}
|
|
||||||
{{codefund-ad placement="topic-above-suggested"}}
|
|
||||||
{{/if}}
|
|
||||||
{{else}}
|
|
||||||
{{#if siteSettings.adsense_topic_above_suggested_code}}
|
|
||||||
{{google-adsense placement="topic-above-suggested"}}
|
|
||||||
{{/if}}
|
|
||||||
{{#if siteSettings.dfp_topic_above_suggested_code}}
|
|
||||||
{{google-dfp-ad placement="topic-above-suggested" refreshOnChange=model.id category=model.category.slug}}
|
|
||||||
{{/if}}
|
|
||||||
{{#if siteSettings.amazon_topic_above_suggested_src_code}}
|
|
||||||
{{amazon-product-links placement="topic-above-suggested"}}
|
|
||||||
{{/if}}
|
|
||||||
{{#if siteSettings.codefund_above_suggested_enabled}}
|
|
||||||
{{codefund-ad placement="topic-above-suggested"}}
|
|
||||||
{{/if}}
|
|
||||||
{{/if}}
|
|
||||||
|
|
|
@ -4,39 +4,11 @@ import { withPluginApi } from "discourse/lib/plugin-api";
|
||||||
export default {
|
export default {
|
||||||
name: "initialize-ad-plugin",
|
name: "initialize-ad-plugin",
|
||||||
initialize(container) {
|
initialize(container) {
|
||||||
const siteSettings = container.lookup("site-settings:main");
|
|
||||||
|
|
||||||
PostModel.reopen({
|
|
||||||
postSpecificCountDFP: function() {
|
|
||||||
return this.isNthPost(parseInt(siteSettings.dfp_nth_post_code));
|
|
||||||
}.property("post_number"),
|
|
||||||
|
|
||||||
postSpecificCountAdsense: function() {
|
|
||||||
return this.isNthPost(parseInt(siteSettings.adsense_nth_post_code));
|
|
||||||
}.property("post_number"),
|
|
||||||
|
|
||||||
postSpecificCountAmazon: function() {
|
|
||||||
return this.isNthPost(parseInt(siteSettings.amazon_nth_post_code));
|
|
||||||
}.property("post_number"),
|
|
||||||
|
|
||||||
postSpecificCountCodeFund: function() {
|
|
||||||
return this.isNthPost(parseInt(siteSettings.codefund_nth_post));
|
|
||||||
}.property("post_number"),
|
|
||||||
|
|
||||||
isNthPost: function(n) {
|
|
||||||
if (n && n > 0) {
|
|
||||||
return this.get("post_number") % n === 0;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
withPluginApi("0.1", api => {
|
withPluginApi("0.1", api => {
|
||||||
api.decorateWidget("post:after", dec => {
|
api.decorateWidget("post:after", dec => {
|
||||||
if (dec.canConnectComponent) {
|
if (dec.canConnectComponent) {
|
||||||
return dec.connect({
|
return dec.connect({
|
||||||
component: "adplugin-container",
|
component: "post-bottom-ad",
|
||||||
context: "model"
|
context: "model"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -48,5 +20,12 @@ export default {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const messageBus = container.lookup('message-bus:main');
|
||||||
|
if (!messageBus) { return; }
|
||||||
|
|
||||||
|
messageBus.subscribe("/site/house-creatives", function (houseAdsSettings) {
|
||||||
|
Discourse.Site.currentProp("house_creatives", houseAdsSettings);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -106,6 +106,15 @@ and (max-width : 775px) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.house-creative {
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.house-creative.house-post-bottom {
|
||||||
|
margin: 0 0 10px 52px;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
.codefund-wrapper {
|
.codefund-wrapper {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
font-family: system, "Helvetica Neue", Helvetica, Arial;
|
font-family: system, "Helvetica Neue", Helvetica, Arial;
|
||||||
|
@ -222,3 +231,56 @@ and (max-width : 775px) {
|
||||||
letter-spacing: 1px;
|
letter-spacing: 1px;
|
||||||
color: $quaternary !important;
|
color: $quaternary !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.adplugin-mgmt {
|
||||||
|
.house-ads-actions {
|
||||||
|
.btn {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.house-ads-list {
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
.house-ads-settings {
|
||||||
|
.form-horizontal {
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
p.help {
|
||||||
|
margin: 0;
|
||||||
|
margin-top: 5px;
|
||||||
|
color: $primary-medium;
|
||||||
|
font-size: $font-down-1;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
.house-ads-chooser, .house-ads-text-input {
|
||||||
|
float: left;
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
.setting-controls {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.house-ads-list-setting {
|
||||||
|
margin-bottom: 1.5em;
|
||||||
|
}
|
||||||
|
.content-body {
|
||||||
|
padding-left: 2%;
|
||||||
|
.controls {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
.delete-button {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
.ace-wrapper {
|
||||||
|
position: relative;
|
||||||
|
height: 270px;
|
||||||
|
.ace_editor {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -12,3 +12,28 @@ en:
|
||||||
amazon_plugin: 'Amazon'
|
amazon_plugin: 'Amazon'
|
||||||
codefund_plugin: 'CodeFund'
|
codefund_plugin: 'CodeFund'
|
||||||
carbonads_plugin: 'Carbon Ads'
|
carbonads_plugin: 'Carbon Ads'
|
||||||
|
adplugin:
|
||||||
|
house_ads:
|
||||||
|
title: "House Ads"
|
||||||
|
new: "New"
|
||||||
|
settings: "Settings"
|
||||||
|
new_name: "New House Ad"
|
||||||
|
save: "Save"
|
||||||
|
delete: "Delete"
|
||||||
|
description: "Define your own ads and where they should be displayed."
|
||||||
|
get_started: "Start by creating a new ad."
|
||||||
|
filter_placeholder: "Select ads..."
|
||||||
|
more_settings: "More Settings"
|
||||||
|
|
||||||
|
topic_list_top:
|
||||||
|
title: "Topic list top ads"
|
||||||
|
description: "Ads to show at the top of topic list pages."
|
||||||
|
topic_above_post_stream:
|
||||||
|
title: "Topic above post stream"
|
||||||
|
description: "Ads to show above the title of a topic on the topic page."
|
||||||
|
topic_above_suggested:
|
||||||
|
title: "Topic above suggested"
|
||||||
|
description: "Ads to show after the last post in a topic, above the suggested topics."
|
||||||
|
post_bottom:
|
||||||
|
title: "Between posts"
|
||||||
|
description: "Ads to show in between posts, after every N posts."
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
en:
|
en:
|
||||||
site_settings:
|
site_settings:
|
||||||
no_ads_for_groups: "Don't show ads to users in these groups."
|
no_ads_for_groups: "Don't show ads to users in these groups."
|
||||||
|
house_ads_after_nth_post: 'If "Between posts" house ads are defined, show an ad after every N posts, where N is this value.'
|
||||||
|
|
||||||
dfp_publisher_id: "Input your Google Ad Manager (formerly called DFP) network code, which is found in your network settings."
|
dfp_publisher_id: "Input your Google Ad Manager (formerly called DFP) network code, which is found in your network settings."
|
||||||
dfp_through_trust_level: "Show your ads to users based on trust levels. Users with trust level higher than this value will not see ads."
|
dfp_through_trust_level: "Show your ads to users based on trust levels. Users with trust level higher than this value will not see ads."
|
||||||
|
|
|
@ -3,6 +3,11 @@ ad_plugin:
|
||||||
client: true
|
client: true
|
||||||
default: ""
|
default: ""
|
||||||
type: group_list
|
type: group_list
|
||||||
|
house_ads_after_nth_post:
|
||||||
|
client: true
|
||||||
|
default: 20
|
||||||
|
min: 1
|
||||||
|
max: 10000
|
||||||
|
|
||||||
adsense_plugin:
|
adsense_plugin:
|
||||||
adsense_publisher_code:
|
adsense_publisher_code:
|
||||||
|
|
41
plugin.rb
41
plugin.rb
|
@ -6,8 +6,37 @@
|
||||||
|
|
||||||
register_asset "stylesheets/adplugin.scss"
|
register_asset "stylesheets/adplugin.scss"
|
||||||
|
|
||||||
|
add_admin_route 'admin.adplugin.house_ads.title', 'houseAds'
|
||||||
|
|
||||||
|
module ::AdPlugin
|
||||||
|
def self.plugin_name
|
||||||
|
'discourse-adplugin'.freeze
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.pstore_get(key)
|
||||||
|
PluginStore.get(AdPlugin.plugin_name, key)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.pstore_set(key, value)
|
||||||
|
PluginStore.set(AdPlugin.plugin_name, key, value)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.pstore_delete(key)
|
||||||
|
PluginStore.remove(AdPlugin.plugin_name, key)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
after_initialize do
|
after_initialize do
|
||||||
|
require_dependency File.expand_path('../app/models/house_ad', __FILE__)
|
||||||
|
require_dependency File.expand_path('../app/models/house_ad_setting', __FILE__)
|
||||||
|
require_dependency File.expand_path('../app/controllers/house_ads_controller', __FILE__)
|
||||||
|
require_dependency File.expand_path('../app/controllers/house_ad_settings_controller', __FILE__)
|
||||||
require_dependency 'application_controller'
|
require_dependency 'application_controller'
|
||||||
|
|
||||||
|
add_to_serializer :site, :house_creatives do
|
||||||
|
AdPlugin::HouseAdSetting.settings_and_ads
|
||||||
|
end
|
||||||
|
|
||||||
class ::AdstxtController < ::ApplicationController
|
class ::AdstxtController < ::ApplicationController
|
||||||
skip_before_action :check_xhr
|
skip_before_action :check_xhr
|
||||||
|
|
||||||
|
@ -18,7 +47,19 @@ after_initialize do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class AdPlugin::Engine < ::Rails::Engine
|
||||||
|
engine_name 'adplugin'
|
||||||
|
isolate_namespace AdPlugin
|
||||||
|
end
|
||||||
|
|
||||||
|
AdPlugin::Engine.routes.draw do
|
||||||
|
root to: 'house_ads#index'
|
||||||
|
resources :house_ads, only: [:index, :create, :update, :destroy]
|
||||||
|
resources :house_ad_settings, only: [:update]
|
||||||
|
end
|
||||||
|
|
||||||
Discourse::Application.routes.append do
|
Discourse::Application.routes.append do
|
||||||
get '/ads.txt' => "adstxt#index"
|
get '/ads.txt' => "adstxt#index"
|
||||||
|
mount ::AdPlugin::Engine, at: '/admin/plugins/adplugin', constraints: AdminConstraint.new
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
describe AdPlugin::HouseAdSetting do
|
||||||
|
let(:defaults) { AdPlugin::HouseAdSetting::DEFAULTS }
|
||||||
|
|
||||||
|
describe '#all' do
|
||||||
|
subject { AdPlugin::HouseAdSetting.all }
|
||||||
|
|
||||||
|
it "returns defaults when nothing has been set" do
|
||||||
|
expect(subject).to eq(defaults)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns defaults and overrides" do
|
||||||
|
AdPlugin::pstore_set('ad-setting:topic_list_top', 'Banner')
|
||||||
|
expect(subject[:topic_list_top]).to eq('Banner')
|
||||||
|
expect(subject.except(:topic_list_top)).to eq(
|
||||||
|
defaults.except(:topic_list_top)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#update' do
|
||||||
|
before do
|
||||||
|
AdPlugin::HouseAd.create(name: "Banner", html: "<p>Banner</p>")
|
||||||
|
AdPlugin::HouseAd.create(name: "Donate", html: "<p>Donate</p>")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "can set override for the first time" do
|
||||||
|
expect {
|
||||||
|
AdPlugin::HouseAdSetting.update(:topic_list_top, 'Banner|Donate')
|
||||||
|
}.to change { PluginStoreRow.count }.by(1)
|
||||||
|
expect(AdPlugin::HouseAdSetting.all[:topic_list_top]).to eq('Banner|Donate')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "can update an existing override" do
|
||||||
|
AdPlugin::pstore_set('ad-setting:topic_list_top', 'Banner')
|
||||||
|
expect {
|
||||||
|
AdPlugin::HouseAdSetting.update(:topic_list_top, 'Banner|Donate')
|
||||||
|
}.to_not change { PluginStoreRow.count }
|
||||||
|
expect(AdPlugin::HouseAdSetting.all[:topic_list_top]).to eq('Banner|Donate')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "removes ad names that don't exist" do
|
||||||
|
AdPlugin::HouseAdSetting.update(:topic_list_top, 'Coupon|Banner|Donate')
|
||||||
|
expect(AdPlugin::HouseAdSetting.all[:topic_list_top]).to eq('Banner|Donate')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "can reset to default" do
|
||||||
|
AdPlugin::pstore_set('ad-setting:topic_list_top', 'Banner')
|
||||||
|
expect {
|
||||||
|
AdPlugin::HouseAdSetting.update(:topic_list_top, '')
|
||||||
|
}.to change { PluginStoreRow.count }.by(-1)
|
||||||
|
expect(AdPlugin::HouseAdSetting.all[:topic_list_top]).to eq('')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "raises error on invalid setting name" do
|
||||||
|
expect {
|
||||||
|
AdPlugin::HouseAdSetting.update(:nope, 'Click Me')
|
||||||
|
}.to raise_error(Discourse::NotFound)
|
||||||
|
expect(AdPlugin::pstore_get('ad-setting:nope')).to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it "raises error on invalid value" do
|
||||||
|
expect {
|
||||||
|
AdPlugin::HouseAdSetting.update(:topic_list_top, '<script>')
|
||||||
|
}.to raise_error(Discourse::InvalidParameters)
|
||||||
|
expect(AdPlugin::HouseAdSetting.all[:topic_list_top]).to eq('')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,160 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
describe AdPlugin::HouseAd do
|
||||||
|
let(:valid_attrs) { {
|
||||||
|
name: 'Find A Mechanic',
|
||||||
|
html: '<div class="house-ad find-a-mechanic"><a href="https://mechanics.example.com">Find A Mechanic!</a></div>'
|
||||||
|
} }
|
||||||
|
|
||||||
|
describe '#find' do
|
||||||
|
let!(:ad) { AdPlugin::HouseAd.create(valid_attrs) }
|
||||||
|
|
||||||
|
it "returns nil if no match" do
|
||||||
|
expect(AdPlugin::HouseAd.find(100)).to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it "can retrieve by id" do
|
||||||
|
r = AdPlugin::HouseAd.find(ad.id)
|
||||||
|
expect(r&.name).to eq(valid_attrs[:name])
|
||||||
|
expect(r&.html).to eq(valid_attrs[:html])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#all' do
|
||||||
|
it "returns empty array if no records" do
|
||||||
|
expect(AdPlugin::HouseAd.all).to eq([])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns an array of records" do
|
||||||
|
AdPlugin::HouseAd.create(valid_attrs)
|
||||||
|
AdPlugin::HouseAd.create(valid_attrs.merge(name: "Ad 2", html: "<div>Ad 2 Here</div>"))
|
||||||
|
all = AdPlugin::HouseAd.all
|
||||||
|
expect(all.size).to eq(2)
|
||||||
|
expect(all.map(&:name)).to contain_exactly("Ad 2", valid_attrs[:name])
|
||||||
|
expect(all.map(&:html)).to contain_exactly("<div>Ad 2 Here</div>", valid_attrs[:html])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "save" do
|
||||||
|
it "assigns an id and attrs for new record" do
|
||||||
|
ad = AdPlugin::HouseAd.from_hash(valid_attrs)
|
||||||
|
expect(ad.save).to eq(true)
|
||||||
|
expect(ad.name).to eq(valid_attrs[:name])
|
||||||
|
expect(ad.html).to eq(valid_attrs[:html])
|
||||||
|
expect(ad.id.to_i > 0).to eq(true)
|
||||||
|
ad2 = AdPlugin::HouseAd.from_hash(valid_attrs.merge(name: 'Find Another Mechanic'))
|
||||||
|
expect(ad2.save).to eq(true)
|
||||||
|
expect(ad2.id).to_not eq(ad.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "updates existing record" do
|
||||||
|
ad = AdPlugin::HouseAd.create(valid_attrs)
|
||||||
|
id = ad.id
|
||||||
|
ad.name = 'Sell Your Car'
|
||||||
|
ad.html = '<div class="house-ad">Sell Your Car!</div>'
|
||||||
|
expect(ad.save).to eq(true)
|
||||||
|
ad = AdPlugin::HouseAd.find(id)
|
||||||
|
expect(ad.name).to eq('Sell Your Car')
|
||||||
|
expect(ad.html).to eq('<div class="house-ad">Sell Your Car!</div>')
|
||||||
|
expect(ad).to be_valid
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "errors" do
|
||||||
|
it "blank name" do
|
||||||
|
ad = AdPlugin::HouseAd.from_hash(valid_attrs.merge(name: ''))
|
||||||
|
expect(ad.save).to eq(false)
|
||||||
|
expect(ad).to_not be_valid
|
||||||
|
expect(ad.errors.full_messages).to be_present
|
||||||
|
expect(ad.errors[:name]).to be_present
|
||||||
|
expect(ad.errors.count).to eq(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "duplicate name" do
|
||||||
|
existing = AdPlugin::HouseAd.create(valid_attrs)
|
||||||
|
ad = AdPlugin::HouseAd.from_hash(valid_attrs)
|
||||||
|
expect(ad.save).to eq(false)
|
||||||
|
expect(ad).to_not be_valid
|
||||||
|
expect(ad.errors.full_messages).to be_present
|
||||||
|
expect(ad.errors[:name]).to be_present
|
||||||
|
expect(ad.errors.count).to eq(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "duplicate name, different case" do
|
||||||
|
existing = AdPlugin::HouseAd.create(valid_attrs.merge(name: 'mechanic'))
|
||||||
|
ad = AdPlugin::HouseAd.create(valid_attrs.merge(name: 'Mechanic'))
|
||||||
|
expect(ad.save).to eq(false)
|
||||||
|
expect(ad).to_not be_valid
|
||||||
|
expect(ad.errors[:name]).to be_present
|
||||||
|
expect(ad.errors.count).to eq(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "blank html" do
|
||||||
|
ad = AdPlugin::HouseAd.from_hash(valid_attrs.merge(html: ''))
|
||||||
|
expect(ad.save).to eq(false)
|
||||||
|
expect(ad).to_not be_valid
|
||||||
|
expect(ad.errors.full_messages).to be_present
|
||||||
|
expect(ad.errors[:html]).to be_present
|
||||||
|
expect(ad.errors.count).to eq(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "invalid name" do
|
||||||
|
ad = AdPlugin::HouseAd.from_hash(valid_attrs.merge(name: '<script>'))
|
||||||
|
expect(ad.save).to eq(false)
|
||||||
|
expect(ad).to_not be_valid
|
||||||
|
expect(ad.errors[:name]).to be_present
|
||||||
|
expect(ad.errors.count).to eq(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'create' do
|
||||||
|
it "can create new records" do
|
||||||
|
ad = AdPlugin::HouseAd.create(valid_attrs)
|
||||||
|
expect(ad).to be_a(AdPlugin::HouseAd)
|
||||||
|
expect(ad.id).to be_present
|
||||||
|
expect(ad.name).to eq(valid_attrs[:name])
|
||||||
|
expect(ad.html).to eq(valid_attrs[:html])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "validates attributes" do
|
||||||
|
ad = AdPlugin::HouseAd.create({name: '', html: ''})
|
||||||
|
expect(ad).to be_a(AdPlugin::HouseAd)
|
||||||
|
expect(ad).to_not be_valid
|
||||||
|
expect(ad.errors.full_messages).to be_present
|
||||||
|
expect(ad.errors.count).to eq(2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'destroy' do
|
||||||
|
it "can delete a record" do
|
||||||
|
ad = AdPlugin::HouseAd.create(valid_attrs)
|
||||||
|
ad.destroy
|
||||||
|
expect(AdPlugin::HouseAd.find(ad.id)).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'update' do
|
||||||
|
let(:ad) { AdPlugin::HouseAd.create(valid_attrs) }
|
||||||
|
|
||||||
|
it "updates existing record" do
|
||||||
|
expect(
|
||||||
|
ad.update(
|
||||||
|
name: 'Mechanics 4 Hire',
|
||||||
|
html: '<a href="https://mechanics.example.com">Find A Mechanic!</a>'
|
||||||
|
)
|
||||||
|
).to eq(true)
|
||||||
|
after_save = AdPlugin::HouseAd.find(ad.id)
|
||||||
|
expect(after_save.name).to eq('Mechanics 4 Hire')
|
||||||
|
expect(after_save.html).to eq('<a href="https://mechanics.example.com">Find A Mechanic!</a>')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "validates attributes" do
|
||||||
|
expect(
|
||||||
|
ad.update(name: '', html: '')
|
||||||
|
).to eq(false)
|
||||||
|
expect(ad).to_not be_valid
|
||||||
|
expect(ad.errors.full_messages).to be_present
|
||||||
|
expect(ad.errors.count).to eq(2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,46 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
describe AdPlugin::HouseAdSettingsController do
|
||||||
|
let(:admin) { Fabricate(:admin) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
AdPlugin::HouseAd.create(name: "Banner", html: "<p>Banner</p>")
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "update" do
|
||||||
|
let(:valid_params) { { value: 'Banner' } }
|
||||||
|
|
||||||
|
it "error if not logged in" do
|
||||||
|
put '/admin/plugins/adplugin/house_ad_settings/topic_list_top.json', params: valid_params
|
||||||
|
expect(response.status).to eq(404)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "error if not staff" do
|
||||||
|
sign_in(Fabricate(:user))
|
||||||
|
put '/admin/plugins/adplugin/house_ad_settings/topic_list_top.json', params: valid_params
|
||||||
|
expect(response.status).to eq(404)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "logged in as admin" do
|
||||||
|
before do
|
||||||
|
sign_in(admin)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "changes the setting" do
|
||||||
|
put '/admin/plugins/adplugin/house_ad_settings/topic_list_top.json', params: valid_params
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
expect(AdPlugin::HouseAdSetting.all[:topic_list_top]).to eq(valid_params[:value])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "errors on invalid setting name" do
|
||||||
|
put '/admin/plugins/adplugin/house_ad_settings/nope-nope.json', params: valid_params
|
||||||
|
expect(response.status).to eq(404)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "errors on invalid setting value" do
|
||||||
|
put '/admin/plugins/adplugin/house_ad_settings/topic_list_top.json', params: valid_params.merge(value: "Banner|<script>")
|
||||||
|
expect(response.status).to eq(400)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -16,6 +16,18 @@ acceptance("AdSense", {
|
||||||
adsense_mobile_post_bottom_code: "mobile_post_bottom_ad_unit",
|
adsense_mobile_post_bottom_code: "mobile_post_bottom_ad_unit",
|
||||||
adsense_mobile_post_bottom_ad_size: "300*250 - medium rectangle",
|
adsense_mobile_post_bottom_ad_size: "300*250 - medium rectangle",
|
||||||
adsense_nth_post_code: 6
|
adsense_nth_post_code: 6
|
||||||
|
},
|
||||||
|
site: {
|
||||||
|
house_creatives: {
|
||||||
|
settings: {
|
||||||
|
topic_list_top: "",
|
||||||
|
topic_above_post_stream: "",
|
||||||
|
topic_above_suggested: "",
|
||||||
|
post_bottom: "",
|
||||||
|
after_nth_post: 20
|
||||||
|
},
|
||||||
|
creatives: {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,18 @@ acceptance("DFP Ads", {
|
||||||
dfp_mobile_post_bottom_code: "mobile_post_bottom_ad_unit",
|
dfp_mobile_post_bottom_code: "mobile_post_bottom_ad_unit",
|
||||||
dfp_mobile_post_bottom_ad_size: "300*250 - medium rectangle",
|
dfp_mobile_post_bottom_ad_size: "300*250 - medium rectangle",
|
||||||
dfp_nth_post_code: 6
|
dfp_nth_post_code: 6
|
||||||
|
},
|
||||||
|
site: {
|
||||||
|
house_creatives: {
|
||||||
|
settings: {
|
||||||
|
topic_list_top: "",
|
||||||
|
topic_above_post_stream: "",
|
||||||
|
topic_above_suggested: "",
|
||||||
|
post_bottom: "",
|
||||||
|
after_nth_post: 20
|
||||||
|
},
|
||||||
|
creatives: {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
import { acceptance, replaceCurrentUser } from "helpers/qunit-helpers";
|
||||||
|
|
||||||
|
acceptance("House Ads", {
|
||||||
|
loggedIn: true,
|
||||||
|
settings: {
|
||||||
|
house_ads_after_nth_post: 6
|
||||||
|
},
|
||||||
|
site: {
|
||||||
|
house_creatives: {
|
||||||
|
settings: {
|
||||||
|
topic_list_top: "Topic List",
|
||||||
|
topic_above_post_stream: "Above Post Stream",
|
||||||
|
topic_above_suggested: "Above Suggested",
|
||||||
|
post_bottom: "Post",
|
||||||
|
after_nth_post: 6
|
||||||
|
},
|
||||||
|
creatives: {
|
||||||
|
"Topic List": "<div class='h-topic-list'>TOPIC LIST</div>",
|
||||||
|
"Above Post Stream":
|
||||||
|
"<div class='h-above-post-stream'>ABOVE POST STREAM</div>",
|
||||||
|
"Above Suggested":
|
||||||
|
"<div class='h-above-suggested'>ABOVE SUGGESTED</div>",
|
||||||
|
Post: "<div class='h-post'>BELOW POST</div>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("correct ads show", async assert => {
|
||||||
|
replaceCurrentUser({ staff: false, trust_level: 1 });
|
||||||
|
await visit("/t/280"); // 20 posts
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
find(".h-above-post-stream").length,
|
||||||
|
1,
|
||||||
|
"it should render ad at top of topic"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
find(".h-above-suggested").length,
|
||||||
|
1,
|
||||||
|
"it should render ad above suggested topics"
|
||||||
|
);
|
||||||
|
|
||||||
|
const ads = find(".h-post");
|
||||||
|
assert.equal(ads.length, 3, "it should render 3 ads");
|
||||||
|
assert.equal(
|
||||||
|
find("#post_6 + .widget-connector").find(".h-post").length,
|
||||||
|
1,
|
||||||
|
"ad after 6th post"
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
find("#post_12 + .widget-connector").find(".h-post").length,
|
||||||
|
1,
|
||||||
|
"ad after 12th post"
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
find("#post_18 + .widget-connector").find(".h-post").length,
|
||||||
|
1,
|
||||||
|
"ad after 18th post"
|
||||||
|
);
|
||||||
|
|
||||||
|
await visit("/latest");
|
||||||
|
assert.equal(
|
||||||
|
find(".h-topic-list").length,
|
||||||
|
1,
|
||||||
|
"it should render ad above topic list"
|
||||||
|
);
|
||||||
|
});
|
Loading…
Reference in New Issue