diff --git a/app/controllers/house_ad_settings_controller.rb b/app/controllers/house_ad_settings_controller.rb
new file mode 100644
index 0000000..62d6b73
--- /dev/null
+++ b/app/controllers/house_ad_settings_controller.rb
@@ -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
diff --git a/app/controllers/house_ads_controller.rb b/app/controllers/house_ads_controller.rb
new file mode 100644
index 0000000..b596180
--- /dev/null
+++ b/app/controllers/house_ads_controller.rb
@@ -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
diff --git a/app/models/house_ad.rb b/app/models/house_ad.rb
new file mode 100644
index 0000000..ab467de
--- /dev/null
+++ b/app/models/house_ad.rb
@@ -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 = "
New Ad
"
+ 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
diff --git a/app/models/house_ad_setting.rb b/app/models/house_ad_setting.rb
new file mode 100644
index 0000000..fac5a02
--- /dev/null
+++ b/app/models/house_ad_setting.rb
@@ -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
diff --git a/assets/javascripts/discourse/adplugin-route-map.js.es6 b/assets/javascripts/discourse/adplugin-route-map.js.es6
new file mode 100644
index 0000000..bd72f49
--- /dev/null
+++ b/assets/javascripts/discourse/adplugin-route-map.js.es6
@@ -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" });
+ });
+ }
+};
diff --git a/assets/javascripts/discourse/components/ad_component.js.es6 b/assets/javascripts/discourse/components/ad-component.js.es6
similarity index 72%
rename from assets/javascripts/discourse/components/ad_component.js.es6
rename to assets/javascripts/discourse/components/ad-component.js.es6
index 13f0799..c70cbb1 100644
--- a/assets/javascripts/discourse/components/ad_component.js.es6
+++ b/assets/javascripts/discourse/components/ad-component.js.es6
@@ -1,8 +1,8 @@
import computed from "ember-addons/ember-computed-decorators";
export default Ember.Component.extend({
- @computed('currentUser.groups')
- showToGroups: function(groups) {
+ @computed("currentUser.groups")
+ showToGroups(groups) {
const currentUser = Discourse.User.current();
if (
@@ -17,5 +17,13 @@ export default Ember.Component.extend({
const noAdsGroupNames = this.siteSettings.no_ads_for_groups.split("|");
return !groups.any(group => noAdsGroupNames.includes(group.name));
+ },
+
+ isNthPost(n) {
+ if (n && n > 0) {
+ return this.get("postNumber") % n === 0;
+ } else {
+ return false;
+ }
}
});
diff --git a/assets/javascripts/discourse/components/ad-slot.js.es6 b/assets/javascripts/discourse/components/ad-slot.js.es6
new file mode 100644
index 0000000..297e224
--- /dev/null
+++ b/assets/javascripts/discourse/components/ad-slot.js.es6
@@ -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]);
+ });
+ }
+});
diff --git a/assets/javascripts/discourse/components/amazon-product-links.js.es6 b/assets/javascripts/discourse/components/amazon-product-links.js.es6
index 532fcf0..53e41c3 100644
--- a/assets/javascripts/discourse/components/amazon-product-links.js.es6
+++ b/assets/javascripts/discourse/components/amazon-product-links.js.es6
@@ -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";
const currentUser = Discourse.User.current();
@@ -125,7 +125,11 @@ if (
export default AdComponent.extend({
classNames: ["amazon-product-links"],
- showAd: Ember.computed.and("showToTrustLevel", "showToGroups"),
+ showAd: Ember.computed.and(
+ "showToTrustLevel",
+ "showToGroups",
+ "showAfterPost"
+ ),
init() {
let placement = this.get("placement");
@@ -169,5 +173,14 @@ export default AdComponent.extend({
trustLevel &&
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));
}
});
diff --git a/assets/javascripts/discourse/components/carbonads-ad.js.es6 b/assets/javascripts/discourse/components/carbonads-ad.js.es6
index 1571399..6bd49d6 100644
--- a/assets/javascripts/discourse/components/carbonads-ad.js.es6
+++ b/assets/javascripts/discourse/components/carbonads-ad.js.es6
@@ -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 {
default as computed,
observes
diff --git a/assets/javascripts/discourse/components/codefund-ad.js.es6 b/assets/javascripts/discourse/components/codefund-ad.js.es6
index 9c26db8..973157d 100644
--- a/assets/javascripts/discourse/components/codefund-ad.js.es6
+++ b/assets/javascripts/discourse/components/codefund-ad.js.es6
@@ -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 {
default as computed,
observes
@@ -108,17 +108,26 @@ export default AdComponent.extend({
@computed("currentUser.trust_level")
showToTrustLevel(trustLevel) {
return !(
- trustLevel &&
- trustLevel > Discourse.SiteSettings.codefund_through_trust_level
+ trustLevel && trustLevel > this.siteSettings.codefund_through_trust_level
);
},
- @computed("showToTrustLevel", "showToGroups")
- showAd(showToTrustLevel, showToGroups) {
+ @computed("showToTrustLevel", "showToGroups", "showAfterPost")
+ showAd(showToTrustLevel, showToGroups, showAfterPost) {
return (
- Discourse.SiteSettings.codefund_property_id &&
+ this.siteSettings.codefund_property_id &&
showToTrustLevel &&
- showToGroups
+ showToGroups &&
+ showAfterPost
);
+ },
+
+ @computed("postNumber")
+ showAfterPost(postNumber) {
+ if (!postNumber) {
+ return true;
+ }
+
+ return this.isNthPost(parseInt(this.siteSettings.codefund_nth_post));
}
});
diff --git a/assets/javascripts/discourse/components/google-adsense.js.es6 b/assets/javascripts/discourse/components/google-adsense.js.es6
index e124747..3b31018 100644
--- a/assets/javascripts/discourse/components/google-adsense.js.es6
+++ b/assets/javascripts/discourse/components/google-adsense.js.es6
@@ -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 {
default as computed,
observes
@@ -233,17 +233,26 @@ export default AdComponent.extend({
@computed("currentUser.trust_level")
showToTrustLevel(trustLevel) {
return !(
- trustLevel &&
- trustLevel > Discourse.SiteSettings.adsense_through_trust_level
+ trustLevel && trustLevel > this.siteSettings.adsense_through_trust_level
);
},
- @computed("showToTrustLevel", "showToGroups")
- showAd(showToTrustLevel, showToGroups) {
+ @computed("showToTrustLevel", "showToGroups", "showAfterPost")
+ showAd(showToTrustLevel, showToGroups, showAfterPost) {
return (
+ this.siteSettings.adsense_publisher_code &&
showToTrustLevel &&
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));
}
});
diff --git a/assets/javascripts/discourse/components/google-dfp-ad.js.es6 b/assets/javascripts/discourse/components/google-dfp-ad.js.es6
index b213f85..4e5a3ae 100755
--- a/assets/javascripts/discourse/components/google-dfp-ad.js.es6
+++ b/assets/javascripts/discourse/components/google-dfp-ad.js.es6
@@ -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 {
default as computed,
observes,
@@ -234,23 +234,32 @@ export default AdComponent.extend({
return `width: ${w}px;`.htmlSafe();
},
- @computed("showToTrustLevel", "showToGroups")
- showAd(showToTrustLevel, showToGroups) {
+ @computed("showToTrustLevel", "showToGroups", "showAfterPost")
+ showAd(showToTrustLevel, showToGroups, showAfterPost) {
return (
- Discourse.SiteSettings.dfp_publisher_id &&
+ this.siteSettings.dfp_publisher_id &&
showToTrustLevel &&
- showToGroups
+ showToGroups &&
+ showAfterPost
);
},
@computed("currentUser.trust_level")
showToTrustLevel(trustLevel) {
return !(
- trustLevel &&
- trustLevel > Discourse.SiteSettings.dfp_through_trust_level
+ trustLevel && trustLevel > this.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")
refreshAd() {
var slot = ads[this.get("divId")];
diff --git a/assets/javascripts/discourse/components/house-ad.js.es6 b/assets/javascripts/discourse/components/house-ad.js.es6
new file mode 100644
index 0000000..726110b
--- /dev/null
+++ b/assets/javascripts/discourse/components/house-ad.js.es6
@@ -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();
+ }
+});
diff --git a/assets/javascripts/discourse/components/house-ads-chooser.js.es6 b/assets/javascripts/discourse/components/house-ads-chooser.js.es6
new file mode 100644
index 0000000..f574bc9
--- /dev/null
+++ b/assets/javascripts/discourse/components/house-ads-chooser.js.es6
@@ -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);
+ }
+});
diff --git a/assets/javascripts/discourse/components/house-ads-list-setting.js.es6 b/assets/javascripts/discourse/components/house-ads-list-setting.js.es6
new file mode 100644
index 0000000..69133f7
--- /dev/null
+++ b/assets/javascripts/discourse/components/house-ads-list-setting.js.es6
@@ -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")
+});
diff --git a/assets/javascripts/discourse/components/house-ads-setting.js.es6 b/assets/javascripts/discourse/components/house-ads-setting.js.es6
new file mode 100644
index 0000000..ae87fa5
--- /dev/null
+++ b/assets/javascripts/discourse/components/house-ads-setting.js.es6
@@ -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"));
+ }
+ }
+});
diff --git a/assets/javascripts/discourse/controllers/admin-plugins-house-ads-index.js.es6 b/assets/javascripts/discourse/controllers/admin-plugins-house-ads-index.js.es6
new file mode 100644
index 0000000..9322e96
--- /dev/null
+++ b/assets/javascripts/discourse/controllers/admin-plugins-house-ads-index.js.es6
@@ -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")
+});
diff --git a/assets/javascripts/discourse/controllers/admin-plugins-house-ads-show.js.es6 b/assets/javascripts/discourse/controllers/admin-plugins-house-ads-show.js.es6
new file mode 100644
index 0000000..106d9b8
--- /dev/null
+++ b/assets/javascripts/discourse/controllers/admin-plugins-house-ads-show.js.es6
@@ -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")));
+ }
+ }
+});
diff --git a/assets/javascripts/discourse/controllers/admin-plugins-house-ads.js.es6 b/assets/javascripts/discourse/controllers/admin-plugins-house-ads.js.es6
new file mode 100644
index 0000000..8b22942
--- /dev/null
+++ b/assets/javascripts/discourse/controllers/admin-plugins-house-ads.js.es6
@@ -0,0 +1,3 @@
+export default Ember.Controller.extend({
+ loadingAds: true
+});
diff --git a/assets/javascripts/discourse/routes/admin-plugins-house-ads-index.js.es6 b/assets/javascripts/discourse/routes/admin-plugins-house-ads-index.js.es6
new file mode 100644
index 0000000..6610e77
--- /dev/null
+++ b/assets/javascripts/discourse/routes/admin-plugins-house-ads-index.js.es6
@@ -0,0 +1,7 @@
+export default Discourse.Route.extend({
+ actions: {
+ moreSettings() {
+ this.transitionTo("adminSiteSettingsCategory", "ad_plugin");
+ }
+ }
+});
diff --git a/assets/javascripts/discourse/routes/admin-plugins-house-ads-show.js.es6 b/assets/javascripts/discourse/routes/admin-plugins-house-ads-show.js.es6
new file mode 100644
index 0000000..d82f86a
--- /dev/null
+++ b/assets/javascripts/discourse/routes/admin-plugins-house-ads-show.js.es6
@@ -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)
+ );
+ }
+ }
+});
diff --git a/assets/javascripts/discourse/routes/admin-plugins-house-ads.js.es6 b/assets/javascripts/discourse/routes/admin-plugins-house-ads.js.es6
new file mode 100644
index 0000000..cf41b62
--- /dev/null
+++ b/assets/javascripts/discourse/routes/admin-plugins-house-ads.js.es6
@@ -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
+ });
+ }
+});
diff --git a/assets/javascripts/discourse/templates/admin/plugins-house-ads-index.hbs b/assets/javascripts/discourse/templates/admin/plugins-house-ads-index.hbs
new file mode 100644
index 0000000..3284a0c
--- /dev/null
+++ b/assets/javascripts/discourse/templates/admin/plugins-house-ads-index.hbs
@@ -0,0 +1,23 @@
+{{#d-section class="house-ads-settings content-body"}}
+ {{i18n 'admin.adplugin.house_ads.description'}}
+
+ {{#unless houseAds.length}}
+
+ {{#link-to 'adminPlugins.houseAds.show' 'new'}}
+ {{i18n 'admin.adplugin.house_ads.get_started'}}
+ {{/link-to}}
+
+ {{else}}
+
+ {{/unless}}
+{{/d-section}}
diff --git a/assets/javascripts/discourse/templates/admin/plugins-house-ads-show.hbs b/assets/javascripts/discourse/templates/admin/plugins-house-ads-show.hbs
new file mode 100644
index 0000000..10b553e
--- /dev/null
+++ b/assets/javascripts/discourse/templates/admin/plugins-house-ads-show.hbs
@@ -0,0 +1,26 @@
+{{#d-section class="edit-house-ad content-body"}}
+ {{text-field class="house-ad-name" value=buffered.name}}
+
+ {{ace-editor content=buffered.html mode="html"}}
+
+
+ {{d-button
+ action=(action "save")
+ disabled=disableSave
+ class="btn-primary"
+ label="admin.adplugin.house_ads.save"}}
+
+ {{#if saving}}
+ {{savingStatus}}
+ {{else}}
+ {{#if dirty}}
+
{{i18n 'cancel'}}
+ {{/if}}
+ {{/if}}
+
+ {{d-button
+ action=(action "destroy")
+ class="btn-danger delete-button"
+ label="admin.adplugin.house_ads.delete"}}
+
+{{/d-section}}
diff --git a/assets/javascripts/discourse/templates/admin/plugins-house-ads.hbs b/assets/javascripts/discourse/templates/admin/plugins-house-ads.hbs
new file mode 100644
index 0000000..75e2c70
--- /dev/null
+++ b/assets/javascripts/discourse/templates/admin/plugins-house-ads.hbs
@@ -0,0 +1,27 @@
+
+
{{i18n 'admin.adplugin.house_ads.title'}}
+ {{#if model.length}}
+
+
+ {{#link-to 'adminPlugins.houseAds.show' 'new' class="btn btn-primary"}}
+ {{d-icon "plus"}}
+ {{i18n 'admin.adplugin.house_ads.new'}}
+ {{/link-to}}
+ {{#link-to 'adminPlugins.houseAds.index' class="btn btn-default"}}
+ {{d-icon "cog"}}
+ {{i18n 'admin.adplugin.house_ads.settings'}}
+ {{/link-to}}
+
+
+ {{#each model as |ad|}}
+ -
+ {{#link-to 'adminPlugins.houseAds.show' ad.id}}
+ {{ad.name}}
+ {{/link-to}}
+
+ {{/each}}
+
+
+ {{/if}}
+ {{outlet}}
+
diff --git a/assets/javascripts/discourse/templates/components/ad-slot.hbs b/assets/javascripts/discourse/templates/components/ad-slot.hbs
new file mode 100644
index 0000000..7a49e31
--- /dev/null
+++ b/assets/javascripts/discourse/templates/components/ad-slot.hbs
@@ -0,0 +1,8 @@
+{{#each adComponents as |adComponent|}}
+ {{component adComponent
+ placement=placement
+ refreshOnChange=refreshOnChange
+ category=category
+ listLoading=listLoading
+ postNumber=postNumber}}
+{{/each}}
diff --git a/assets/javascripts/discourse/templates/components/adplugin-container.hbs b/assets/javascripts/discourse/templates/components/adplugin-container.hbs
deleted file mode 100644
index bcffef5..0000000
--- a/assets/javascripts/discourse/templates/components/adplugin-container.hbs
+++ /dev/null
@@ -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}}
diff --git a/assets/javascripts/discourse/templates/components/house-ad.hbs b/assets/javascripts/discourse/templates/components/house-ad.hbs
new file mode 100644
index 0000000..7e97aa9
--- /dev/null
+++ b/assets/javascripts/discourse/templates/components/house-ad.hbs
@@ -0,0 +1,3 @@
+{{#if showAd}}
+ {{{adHtml}}}
+{{/if}}
diff --git a/assets/javascripts/discourse/templates/components/house-ads-list-setting.hbs b/assets/javascripts/discourse/templates/components/house-ads-list-setting.hbs
new file mode 100644
index 0000000..02bf05d
--- /dev/null
+++ b/assets/javascripts/discourse/templates/components/house-ads-list-setting.hbs
@@ -0,0 +1,9 @@
+
+{{house-ads-chooser settingValue=adValue choices=adNames}}
+
+ {{#if changed}}
+ {{d-button class="ok" action=(action "save") icon="check"}}
+ {{d-button class="cancel" action=(action "cancel") icon="times"}}
+ {{/if}}
+
+{{help}}
diff --git a/assets/javascripts/discourse/templates/components/house-ads-setting.hbs b/assets/javascripts/discourse/templates/components/house-ads-setting.hbs
new file mode 100644
index 0000000..b98b53f
--- /dev/null
+++ b/assets/javascripts/discourse/templates/components/house-ads-setting.hbs
@@ -0,0 +1,9 @@
+
+{{text-field value=adValue classNames="house-ads-text-input"}}
+
+ {{#if changed}}
+ {{d-button class="ok" action=(action "save") icon="check"}}
+ {{d-button class="cancel" action=(action "cancel") icon="times"}}
+ {{/if}}
+
+{{help}}
diff --git a/assets/javascripts/discourse/templates/components/post-bottom-ad.hbs b/assets/javascripts/discourse/templates/components/post-bottom-ad.hbs
new file mode 100644
index 0000000..7709c7c
--- /dev/null
+++ b/assets/javascripts/discourse/templates/components/post-bottom-ad.hbs
@@ -0,0 +1 @@
+{{ad-slot placement="post-bottom" category=model.topic.category.slug postNumber=model.post_number}}
diff --git a/assets/javascripts/discourse/templates/connectors/discovery-list-container-top/discourse-adplugin.hbs b/assets/javascripts/discourse/templates/connectors/discovery-list-container-top/discourse-adplugin.hbs
index 3a50085..eec8db3 100644
--- a/assets/javascripts/discourse/templates/connectors/discovery-list-container-top/discourse-adplugin.hbs
+++ b/assets/javascripts/discourse/templates/connectors/discovery-list-container-top/discourse-adplugin.hbs
@@ -1,33 +1 @@
-{{#if site.mobileView}}
- {{#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}}
\ No newline at end of file
+{{ad-slot placement="topic-list-top" refreshOnChange=listLoading category=category.slug listLoading=listLoading}}
diff --git a/assets/javascripts/discourse/templates/connectors/post-bottom/discourse-adplugin.hbs b/assets/javascripts/discourse/templates/connectors/post-bottom/discourse-adplugin.hbs
index ab5aa7d..f0287d1 100644
--- a/assets/javascripts/discourse/templates/connectors/post-bottom/discourse-adplugin.hbs
+++ b/assets/javascripts/discourse/templates/connectors/post-bottom/discourse-adplugin.hbs
@@ -1 +1 @@
-{{adplugin-container model=this}}
+{{post-bottom-ad model=this}}
diff --git a/assets/javascripts/discourse/templates/connectors/topic-above-post-stream/discourse-adplugin.hbs b/assets/javascripts/discourse/templates/connectors/topic-above-post-stream/discourse-adplugin.hbs
index e5d3d74..eea3ea0 100644
--- a/assets/javascripts/discourse/templates/connectors/topic-above-post-stream/discourse-adplugin.hbs
+++ b/assets/javascripts/discourse/templates/connectors/topic-above-post-stream/discourse-adplugin.hbs
@@ -1,33 +1 @@
-{{#if site.mobileView}}
- {{#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}}
\ No newline at end of file
+{{ad-slot placement="topic-above-post-stream" refreshOnChange=model.id category=model.category.slug}}
diff --git a/assets/javascripts/discourse/templates/connectors/topic-above-suggested/discourse-adplugin.hbs b/assets/javascripts/discourse/templates/connectors/topic-above-suggested/discourse-adplugin.hbs
index 8f45084..a90622d 100644
--- a/assets/javascripts/discourse/templates/connectors/topic-above-suggested/discourse-adplugin.hbs
+++ b/assets/javascripts/discourse/templates/connectors/topic-above-suggested/discourse-adplugin.hbs
@@ -1,27 +1 @@
-{{#if site.mobileView}}
- {{#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}}
\ No newline at end of file
+{{ad-slot placement="topic-above-suggested" refreshOnChange=model.id category=model.category.slug}}
diff --git a/assets/javascripts/initializers/initialize-ad-plugin.js.es6 b/assets/javascripts/initializers/initialize-ad-plugin.js.es6
index a6315b8..e42915c 100644
--- a/assets/javascripts/initializers/initialize-ad-plugin.js.es6
+++ b/assets/javascripts/initializers/initialize-ad-plugin.js.es6
@@ -4,39 +4,11 @@ import { withPluginApi } from "discourse/lib/plugin-api";
export default {
name: "initialize-ad-plugin",
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 => {
api.decorateWidget("post:after", dec => {
if (dec.canConnectComponent) {
return dec.connect({
- component: "adplugin-container",
+ component: "post-bottom-ad",
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);
+ });
}
};
diff --git a/assets/stylesheets/adplugin.scss b/assets/stylesheets/adplugin.scss
index 6497d14..9e7bad3 100644
--- a/assets/stylesheets/adplugin.scss
+++ b/assets/stylesheets/adplugin.scss
@@ -7,7 +7,7 @@
clear: both;
}
-.google-dfp-ad .dfp-ad-unit {
+.google-dfp-ad .dfp-ad-unit {
margin: 0 auto;
}
@@ -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 {
z-index: 1;
font-family: system, "Helvetica Neue", Helvetica, Arial;
@@ -222,3 +231,56 @@ and (max-width : 775px) {
letter-spacing: 1px;
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;
+ }
+ }
+ }
+}
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index e45b924..4448240 100755
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -12,3 +12,28 @@ en:
amazon_plugin: 'Amazon'
codefund_plugin: 'CodeFund'
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."
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index cfde9d4..0426c06 100755
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -1,6 +1,7 @@
en:
site_settings:
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_through_trust_level: "Show your ads to users based on trust levels. Users with trust level higher than this value will not see ads."
diff --git a/config/settings.yml b/config/settings.yml
index dfc4004..d3f220d 100755
--- a/config/settings.yml
+++ b/config/settings.yml
@@ -3,6 +3,11 @@ ad_plugin:
client: true
default: ""
type: group_list
+ house_ads_after_nth_post:
+ client: true
+ default: 20
+ min: 1
+ max: 10000
adsense_plugin:
adsense_publisher_code:
diff --git a/plugin.rb b/plugin.rb
index ef620d7..362873a 100755
--- a/plugin.rb
+++ b/plugin.rb
@@ -6,8 +6,37 @@
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
+ 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'
+
+ add_to_serializer :site, :house_creatives do
+ AdPlugin::HouseAdSetting.settings_and_ads
+ end
+
class ::AdstxtController < ::ApplicationController
skip_before_action :check_xhr
@@ -18,7 +47,19 @@ after_initialize do
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
get '/ads.txt' => "adstxt#index"
+ mount ::AdPlugin::Engine, at: '/admin/plugins/adplugin', constraints: AdminConstraint.new
end
end
diff --git a/spec/models/house_ad_setting_spec.rb b/spec/models/house_ad_setting_spec.rb
new file mode 100644
index 0000000..28cfe15
--- /dev/null
+++ b/spec/models/house_ad_setting_spec.rb
@@ -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: "Banner
")
+ AdPlugin::HouseAd.create(name: "Donate", html: "Donate
")
+ 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, '