44 Commits
Author SHA1 Message Date
Penar Musaraj f745ecfbdc Rename adsense_ads_txt setting to ads_txt 2019-06-03 11:39:28 -04:00
Neil Lalonde ab9cf6459e Update translations 2019-05-30 11:30:03 -04:00
Guo Xiang Tan 546101a357 Add frozen string literal to files. 2019-05-13 10:16:50 +08:00
Neil Lalonde d3a5927ede Don't render inside a cloaked post 2019-05-10 14:22:18 -04:00
Neil Lalonde 2ca0adca05 Fix failing js test 2019-05-08 13:44:25 -04:00
Neil Lalonde 896e5a9838 Prettier 2019-05-08 11:22:21 -04:00
Neil Lalonde 10ab104cd8 JS test for mixed ads 2019-05-08 11:17:40 -04:00
Neil Lalonde 365f441924 FIX: groups with mixed case names not working 2019-05-08 11:01:01 -04:00
Neil Lalonde 4bd2035866 FEATURE: house ads can be configured to alternate with other ads
Use the new "house ads frequency" site setting to configure how often
house ads should show, as a percentage. This only applies in ad
placements where other ad networks are configured to appear.
2019-05-07 19:52:51 -04:00
Neil Lalonde cdc14398ba FIX: linking to house ads URLs shows not found error page 2019-05-07 14:32:32 -04:00
Neil Lalonde d9d7f4c8f4 FIX: house ads randomized instead of showing evenly 2019-05-03 16:04:04 -04:00
Neil Lalonde 26d42e9ed7 Fix compatibility with other js tests 2019-05-03 13:26:16 -04:00
Neil Lalonde 171656304b Version bump, update readme with House Ads info 2019-05-03 11:56:59 -04:00
Neil Lalonde 1bd80e1afe 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.
2019-05-03 11:39:34 -04:00
Neil Lalonde 5272995e74 FEATURE: use groups to control who sees ads 2019-05-03 11:39:34 -04:00
Neil Lalonde 369d5dfc9e oops revert again "FEATURE: use groups to control who sees ads" and all later" 2019-04-17 16:02:19 -04:00
Neil Lalonde b105da53e8 disable js tests
works on my machine, but breaks build
2019-04-17 15:53:41 -04:00
Neil Lalonde e6bcd96d8e FEATURE: use groups to control who sees ads" and all later 2019-04-17 15:40:50 -04:00
Neil Lalonde 50d258dc37 Revert "FEATURE: use groups to control who sees ads" and all later
Revert "Updates to codefund-ad"
Revert "Updates to carbonads-ad"
Revert "Updates to amazon-product-links"
2019-04-17 15:37:09 -04:00
Neil Lalonde 63e604d81b Updates to codefund-ad 2019-04-17 11:36:50 -04:00
Neil Lalonde 8ef02aa351 Updates to carbonads-ad 2019-04-17 11:36:50 -04:00
Neil Lalonde 881a8e5d4c Updates to amazon-product-links 2019-04-17 11:36:50 -04:00
Neil Lalonde 4fd7caffd6 FEATURE: use groups to control who sees ads
A new setting has been added in Admin > Settings > Ad Plugin called
"no ads for groups". Add group names to this list. If a user belongs
to any of the groups, they will not see any ads. This is an alternative
to using trust level settings like "adsense through trust level".
2019-04-17 11:36:50 -04:00
Neil Lalonde 6097e6f097 Clean-up codefund-ad component 2019-04-17 11:36:50 -04:00
Neil Lalonde 943acbfeb1 Add some js tests, and update adsense js 2019-04-17 11:36:47 -04:00
Diego Barreiro 8f2e4c5a49 FIX: Wrong Image 2019-04-15 13:54:20 -04:00
Diego Barreiro 147b1350ea FIX: Switch Property to Computed 2019-04-15 11:54:18 -04:00
Diego Barreiro 23ece34f53 FIX: Switch Var to Const 2019-04-15 11:54:18 -04:00
Diego Barreiro 7ee19d79c3 FIX: CSP URLs 2019-04-15 11:54:18 -04:00
Diego Barreiro a1649f7cce BUMP: Plugin Version 2019-04-15 11:54:18 -04:00
Diego Barreiro 79a4c228a6 DEV: Update Readme 2019-04-15 11:54:18 -04:00
Diego Barreiro 608feb2e54 UX: Add Spanish Translations 2019-04-15 11:54:18 -04:00
Diego Barreiro 40c0af2177 DEV: Whitelist CarbonAds URLs on CSP 2019-04-15 11:54:18 -04:00
Diego Barreiro 0980914d0c UI: Style Ads 2019-04-15 11:54:18 -04:00
Diego Barreiro c5272db7f3 UX: Init Carbon Ads 2019-04-15 11:54:18 -04:00
Sam Saffron 4516dbca26 Add missing license file 2019-04-08 17:51:50 +10:00
Neil Lalonde cd35666059 Update translations 2019-04-05 10:08:42 -04:00
Régis Hanol 8fda5c3b7d Delete .DS_Store 2019-03-21 22:10:24 +01:00
Régis Hanol 2065b8bd41 fix new lines in README 2019-03-21 22:09:23 +01:00
Neil Lalonde ec10ef4483 FIX: wrong size setting being used for post-bottom desktop 2019-03-20 11:22:47 -04:00
Neil Lalonde 3f70312c84 Update translations 2019-03-04 10:12:10 -05:00
Neil Lalonde 9b7a8830b8 FIX: resizing of ad units when they're rendered 2019-02-25 09:53:28 -05:00
Neil Lalonde 9a695cc67d refactor google-dfp-ad component to reduce repetition 2019-02-22 11:15:40 -05:00
Penar Musaraj ccad3ca226 FEAT: ads.txt support
Adds a plugin setting that holds text content served on a new /ads.txt route.
2019-02-04 16:14:43 -05:00
123 changed files with 2906 additions and 619 deletions
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
source 'https://rubygems.org'
group :development do
+22
View File
@@ -0,0 +1,22 @@
Copyright (c) 2019 Discourse
MIT License
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+28 -3
View File
@@ -3,15 +3,22 @@
This is the official Discourse advertising plugin. It allows advertisements to be served by supported advertising platforms for users with a Discourse forum.
**Authors**: [Sarah Ni](https://github.com/cyberkoi) & [Vi Nguyen](https://github.com/ladydanger)
**Version**: 1.0.1
**Contributors**: See credits section below
**Version**: 1.2.0
**Contributors**: See [credits section](#credits) below
**License**: MIT License
**Supported Discourse Version**: 1.4
**Supported Ad Platforms**:
* House Ads: Your own ads served from Discourse
* [Google Adsense](http://www.google.com.au/adsense/start/why-adsense.html)
* [Google Double Click for Publishers](https://www.google.com/dfp)
* [Amazon Affiliates](http://affiliate-program.amazon.com) - Banner and Product Link Ads
* [CodeFund](https://codefund.io) - Ethical Ad Platform for Developers
* [Carbon Ads](https://www.carbonads.net/)
## Quick Start in 3 Steps
@@ -50,6 +57,18 @@ git pull
There are 2 easy steps for configuring your Discourse settings to enable advertisements to display in your Discourse forum.
#### House Ads
If you don't want to use an external ad platform, but want to show your own ads, then House Ads are for you!
Define your ads by going to the Admin section of your Discourse forum, and go to the Plugins section.
On the left, you should see a link called "House Ads".
Begin by creating your ads. Give each a short descriptive name and enter the html for each.
Style them using a custom theme (Admin > Customize > Themes).
Lastly, click the Settings button in the House Ads UI and choose which of your ads to show in each of
the ad slots. The ads will start showing as soon as you add them to slots.
#### Step 2(a) - Choose Your Advertisement Platform
<ul>
@@ -57,9 +76,11 @@ There are 2 easy steps for configuring your Discourse settings to enable adverti
<li>Click on Settings and a left vertical navigation bar should appear.</li>
<li>Choose your advertisement platform.</li>
<ul>
<li>House Ads - if you want to create and serve ads from your Discourse instance.</li>
<li>Adsense - if using Adsense as your advertisement platform.</li>
<li>DFP - if using the DoubleClick for Publishers advertisement platform.</li>
<li>CodeFund - if using the CodeFund ethical advertisement platform.</li>
<li>Carbon Ads - if using the Carbon Ads advertisement platform.</li>
</ul>
</ul>
@@ -88,6 +109,10 @@ Only for Product Link and Banner Ads.
![CodeFund Instructions](https://s3-us-west-2.amazonaws.com/codesponsor/discourse-codefund-instructions.png)
##### Carbon Ads Script Tag to Discourse's Site Settings
![Carbon Ads](https://d11a6trkgmumsb.cloudfront.net/original/3X/3/a/3acc7488db2b53733cdd427d3cb1b76361c786e1.png)
### Step 3 - See Your Ad
Once you've configured your settings and your advertising platform has ads that are ready to serve, navigate to the page where you've inputted for the location and you should see ads.
@@ -198,4 +223,4 @@ Open an Issue on this repository to start a chat.
**Our Coaches**: Very special thank you to our coaches and honorary coach - [@georg](https://github.com/georg), [@betaass](https://github.com/betaass), [@adelsmee](https://github.com/adelsmee), [@davich](https://github.com/davich), [@link664](https://github.com/link664), [@tomjadams](https://github.com/tomjadams), [@compactcode](https://github.com/compactcode), [@joffotron](https://github.com/joffotron), [@jocranford](https://github.com/jocranford), [@saramic](https://github.com/saramic), [@madpilot](https://github.com/madpilot), [@catkins](https://github.com/catkins)
**Rails Girls**: Thanks [@sareg0](https://github.com/sareg0) and the Rails Girls Team for the opportunity to participate in Rails Girls Summer of Code 2015.
<p>To create this plugin we referenced the <a href="https://github.com/discourse/discourse-google-dfp">original dfp plugin</a> (created by <a href="https://github.com/search?q=neil+lalonde&ref=opensearch&type=Users">nlalonde</a>) and the <a href="https://meta.discourse.org/t/google-adsense-plugin/11763/133">adsense plugin</a>.</p>
<p>To create this plugin we referenced the <a href="https://github.com/discourse/discourse-google-dfp">original dfp plugin</a> (created by <a href="https://github.com/nlalonde">@nlalonde</a>) and the <a href="https://meta.discourse.org/t/google-adsense-plugin/11763/133">adsense plugin</a>.</p>
@@ -0,0 +1,12 @@
# frozen_string_literal: true
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
+55
View File
@@ -0,0 +1,55 @@
# frozen_string_literal: true
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 show
render_json_dump(house_ad: HouseAd.find(params[:id])&.to_hash)
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
+102
View File
@@ -0,0 +1,102 @@
# frozen_string_literal: true
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
+67
View File
@@ -0,0 +1,67 @@
# frozen_string_literal: true
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,
house_ads_frequency: SiteSetting.house_ads_frequency
),
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
BIN
View File
Binary file not shown.
@@ -0,0 +1,10 @@
export default {
resource: "admin.adminPlugins",
path: "/plugins",
map() {
this.route("houseAds", { path: "/adplugin/house_ads" }, function() {
this.route("index", { path: "/" });
this.route("show", { path: "/:ad_id" });
});
}
};
@@ -0,0 +1,32 @@
import computed from "ember-addons/ember-computed-decorators";
export default Ember.Component.extend({
@computed("currentUser.groups")
showToGroups(groups) {
const currentUser = Discourse.User.current();
if (
!currentUser ||
!groups ||
!this.siteSettings.no_ads_for_groups ||
this.siteSettings.no_ads_for_groups.length === 0
) {
return true;
}
const groupNames = groups.map(g => g.name.toLowerCase());
const noAdsGroupNames = this.siteSettings.no_ads_for_groups
.split("|")
.map(g => g.toLowerCase());
return !groupNames.any(g => noAdsGroupNames.includes(g));
},
isNthPost(n) {
if (n && n > 0) {
return this.get("postNumber") % n === 0;
} else {
return false;
}
}
});
@@ -0,0 +1,188 @@
import AdComponent from "discourse/plugins/discourse-adplugin/discourse/components/ad-component";
import {
default as computed,
observes
} from "ember-addons/ember-computed-decorators";
const adConfig = Ember.Object.create({
"google-adsense": {
settingPrefix: "adsense", // settings follow naming convention
enabledSetting: "adsense_publisher_code",
nthPost: "adsense_nth_post_code"
},
"google-dfp-ad": {
settingPrefix: "dfp", // settings follow naming convention
enabledSetting: "dfp_publisher_id",
nthPost: "dfp_nth_post_code"
},
"amazon-product-links": {
settingPrefix: "amazon",
enabledSetting: false,
nthPost: "amazon_nth_post_code",
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",
enabledSetting: "codefund_property_id",
nthPost: "codefund_nth_post",
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",
enabledSetting: "carbonads_serve_id",
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
}
}
});
const displayCounts = {
houseAds: 0,
allAds: 0
};
export default AdComponent.extend({
needsUpdate: false,
/**
* For a given ad placement and optionally a post number if in between posts,
* list all ad network names that are configured to show there.
*/
@computed("placement", "postNumber")
availableAdTypes(placement, postNumber) {
let types = [];
const houseAds = this.site.get("house_creatives"),
placeUnderscored = placement.replace(/-/g, "_");
if (houseAds && houseAds.settings) {
const adsForSlot = houseAds.settings[placeUnderscored];
if (
Object.keys(houseAds.creatives).length > 0 &&
!Ember.isBlank(adsForSlot) &&
(!postNumber ||
this.isNthPost(parseInt(houseAds.settings.after_nth_post, 10)))
) {
types.push("house-ad");
}
}
Object.keys(adConfig).forEach(adNetwork => {
const config = adConfig[adNetwork];
let settingNames = null,
name;
if (
config.enabledSetting &&
!Ember.isBlank(this.siteSettings[config.enabledSetting]) &&
(!postNumber ||
!config.nthPost ||
this.isNthPost(parseInt(this.siteSettings[config.nthPost], 10)))
) {
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_" : ""
}${placeUnderscored}_code`;
}
if (name !== false && !Ember.isBlank(this.siteSettings[name])) {
types.push(adNetwork);
}
}
});
return types;
},
/**
* When house ads are configured to alternate with other ad networks, we
* need to trigger an update of which ad component is shown after
* navigating between topic lists or topics.
*/
@observes("refreshOnChange")
changed() {
if (this.get("listLoading")) {
return;
}
// force adComponents to be recomputed
this.notifyPropertyChange("needsUpdate");
},
/**
* Returns a list of the names of ad components that should be rendered
* in the given ad placement. It handles alternating between house ads
* and other ad networks.
*/
@computed("placement", "availableAdTypes", "needsUpdate")
adComponents(placement, availableAdTypes) {
if (
!availableAdTypes.includes("house-ad") ||
availableAdTypes.length === 1
) {
// Current behaviour is to allow multiple ads from different networks
// to show in the same place. We could change this to choose one somehow.
return availableAdTypes;
}
const houseAds = this.site.get("house_creatives");
let houseAdsSkipped = false;
if (houseAds.settings.house_ads_frequency === 100) {
// house always wins
return ["house-ad"];
} else if (houseAds.settings.house_ads_frequency > 0) {
// show house ads the given percent of the time
if (
displayCounts.allAds === 0 ||
(100 * displayCounts.houseAds) / displayCounts.allAds <
houseAds.settings.house_ads_frequency
) {
displayCounts.houseAds += 1;
displayCounts.allAds += 1;
return ["house-ad"];
} else {
houseAdsSkipped = true;
}
}
const networkNames = availableAdTypes.filter(x => x !== "house-ad");
if (houseAdsSkipped) {
displayCounts.allAds += networkNames.length;
}
return networkNames;
}
});
@@ -1,6 +1,9 @@
var currentUser = Discourse.User.current();
import AdComponent from "discourse/plugins/discourse-adplugin/discourse/components/ad-component";
import computed from "ember-addons/ember-computed-decorators";
var data = {
const currentUser = Discourse.User.current();
const data = {
"topic-list-top": {},
"topic-above-post-stream": {},
"topic-above-suggested": {},
@@ -119,10 +122,16 @@ if (
);
}
export default Ember.Component.extend({
export default AdComponent.extend({
classNames: ["amazon-product-links"],
init: function() {
showAd: Ember.computed.and(
"showToTrustLevel",
"showToGroups",
"showAfterPost"
),
init() {
let placement = this.get("placement");
this.set("user_input", data[placement]["user_input"]);
this.set("amazon_width", data[placement]["amazon_width"]);
@@ -133,35 +142,45 @@ export default Ember.Component.extend({
this._super();
},
adWrapperStyle: function() {
return `width: ${this.get("amazon_width")}px; height: ${this.get(
"amazon_height"
)}px;`.htmlSafe();
}.property("amazon_width", "amazon_height"),
@computed("amazon_width", "amazon_height")
adWrapperStyle(w, h) {
return `width: ${w}px; height: ${h}px;`.htmlSafe();
},
adWrapperStyleMobile: function() {
return `width: ${this.get("mobile_amazon_width")}px; height: ${this.get(
"mobile_amazon_height"
)}px;`.htmlSafe();
}.property("mobile_amazon_width", "mobile_amazon_height"),
@computed("mobile_amazon_width", "mobile_amazon_height")
adWrapperStyleMobile(w, h) {
return `width: ${w}px; height: ${h}px;`.htmlSafe();
},
adTitleStyleMobile: function() {
return `width: ${this.get("mobile_amazon_width")}px;`.htmlSafe();
}.property("mobile_amazon_width"),
@computed("mobile_amazon_width")
adTitleStyleMobile(w) {
return `width: ${w}px;`.htmlSafe();
},
userInput: function() {
return `${this.get("user_input")}`.htmlSafe();
}.property("user_input"),
@computed("user_input")
userInput(userInput) {
return `${userInput}`.htmlSafe();
},
userInputMobile: function() {
return `${this.get("user_input_mobile")}`.htmlSafe();
}.property("user_input_mobile"),
@computed("user_input_mobile")
userInputMobile(userInput) {
return `${userInput}`.htmlSafe();
},
checkTrustLevels: function() {
@computed("currentUser.trust_level")
showToTrustLevel(trustLevel) {
return !(
currentUser &&
currentUser.get("trust_level") >
Discourse.SiteSettings.amazon_through_trust_level
trustLevel &&
trustLevel > Discourse.SiteSettings.amazon_through_trust_level
);
}.property("trust_level")
},
@computed("postNumber")
showAfterPost(postNumber) {
if (!postNumber) {
return true;
}
return this.isNthPost(parseInt(this.siteSettings.amazon_nth_post_code));
}
});
@@ -0,0 +1,35 @@
import AdComponent from "discourse/plugins/discourse-adplugin/discourse/components/ad-component";
import {
default as computed,
observes
} from "ember-addons/ember-computed-decorators";
const currentUser = Discourse.User.current(),
serve_id = Discourse.SiteSettings.carbonads_serve_id,
placement = Discourse.SiteSettings.carbonads_placement;
export default AdComponent.extend({
init() {
this.set("serve_id", serve_id);
this.set("placement", placement);
this._super();
},
@computed("serve_id", "placement")
url(serveId, placement) {
return `//cdn.carbonads.com/carbon.js?serve=${serveId}&placement=${placement}`.htmlSafe();
},
@computed("currentUser.trust_level")
showToTrustLevel(trustLevel) {
return !(
trustLevel &&
trustLevel > Discourse.SiteSettings.carbonads_through_trust_level
);
},
@computed("showToTrustLevel", "showToGroups")
showAd(showToTrustLevel, showToGroups) {
return placement && serve_id && showToTrustLevel && showToGroups;
}
});
@@ -1,11 +1,13 @@
import AdComponent from "discourse/plugins/discourse-adplugin/discourse/components/ad-component";
import {
default as computed,
observes
} from "ember-addons/ember-computed-decorators";
var _loaded = false,
_promise = null,
currentUser = Discourse.User.current(),
let _loaded = false,
_promise = null;
const currentUser = Discourse.User.current(),
propertyId = Discourse.SiteSettings.codefund_property_id;
function loadCodeFund() {
@@ -48,16 +50,23 @@ function loadCodeFund() {
return _promise;
}
export default Ember.Component.extend({
classNameBindings: [
":codefund-ad",
"classForSlot",
"isResponsive:codefund-responsive"
],
export default AdComponent.extend({
classNameBindings: [":codefund-ad"],
propertyId: propertyId,
adRequested: false,
adDetails: {},
displayPostBottom: Ember.computed.equal("placement", "post-bottom"),
displayTopicAbovePostStream: Ember.computed.equal(
"placement",
"topic-above-post-stream"
),
displayTopicAboveSuggested: Ember.computed.equal(
"placement",
"topic-above-suggested"
),
displayTopicListTop: Ember.computed.equal("placement", "topic-list-top"),
_triggerAds() {
if (!propertyId) return;
@@ -87,7 +96,7 @@ export default Ember.Component.extend({
},
@observes("listLoading")
waitForLoad: function() {
waitForLoad() {
if (this.get("adRequested")) {
return;
} // already requested that this ad unit be populated
@@ -96,36 +105,29 @@ export default Ember.Component.extend({
}
},
checkTrustLevels: function() {
@computed("currentUser.trust_level")
showToTrustLevel(trustLevel) {
return !(
currentUser &&
currentUser.get("trust_level") >
Discourse.SiteSettings.codefund_through_trust_level
trustLevel && trustLevel > this.siteSettings.codefund_through_trust_level
);
}.property("trust_level"),
@computed("checkTrustLevels")
showAd: function(checkTrustLevels) {
return Discourse.SiteSettings.codefund_property_id && checkTrustLevels;
},
@computed("placement")
displayPostBottom: function(placement) {
return placement === "post-bottom";
@computed("showToTrustLevel", "showToGroups", "showAfterPost")
showAd(showToTrustLevel, showToGroups, showAfterPost) {
return (
this.siteSettings.codefund_property_id &&
showToTrustLevel &&
showToGroups &&
showAfterPost
);
},
@computed("placement")
displayTopicAbovePostStream: function() {
return this.get("placement") === "topic-above-post-stream";
},
@computed("postNumber")
showAfterPost(postNumber) {
if (!postNumber) {
return true;
}
@computed("placement")
displayTopicAboveSuggested: function() {
return this.get("placement") === "topic-above-suggested";
},
@computed("placement")
displayTopicListTop: function() {
return this.get("placement") === "topic-list-top";
return this.isNthPost(parseInt(this.siteSettings.codefund_nth_post));
}
});
@@ -1,6 +1,11 @@
import AdComponent from "discourse/plugins/discourse-adplugin/discourse/components/ad-component";
import {
default as computed,
observes
} from "ember-addons/ember-computed-decorators";
import loadScript from "discourse/lib/load-script";
var _loaded = false,
let _loaded = false,
_promise = null,
currentUser = Discourse.User.current(),
publisher_id = Discourse.SiteSettings.adsense_publisher_code;
@@ -30,7 +35,7 @@ function loadAdsense() {
return _promise;
}
var adsenseSrc =
const adsenseSrc =
("https:" === document.location.protocol ? "https:" : "http:") +
"//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js";
_promise = loadScript(adsenseSrc, { scriptTag: true }).then(function() {
@@ -40,7 +45,7 @@ function loadAdsense() {
return _promise;
}
var data = {
let data = {
"topic-list-top": {},
"topic-above-post-stream": {},
"topic-above-suggested": {},
@@ -142,7 +147,7 @@ if (Discourse.SiteSettings.adsense_publisher_code) {
}
}
export default Ember.Component.extend({
export default AdComponent.extend({
classNameBindings: [
":google-adsense",
"classForSlot",
@@ -188,52 +193,66 @@ export default Ember.Component.extend({
Ember.run.scheduleOnce("afterRender", this, this._triggerAds);
},
waitForLoad: function() {
@observes("listLoading")
waitForLoad() {
if (this.get("adRequested")) {
return;
} // already requested that this ad unit be populated
if (!this.get("listLoading")) {
Ember.run.scheduleOnce("afterRender", this, this._triggerAds);
}
}.observes("listLoading"),
},
isResponsive: function() {
return this.get("ad_width") === "auto";
}.property("ad_width"),
@computed("ad_width")
isResponsive(adWidth) {
return adWidth === "auto";
},
classForSlot: function() {
return `adsense-${this.get("placement")}`.htmlSafe();
}.property("placement"),
@computed("placement", "showAd")
classForSlot(placement, showAd) {
return showAd ? `adsense-${placement}`.htmlSafe() : "";
},
autoAdFormat: function() {
return this.get("isResponsive") ? "auto".htmlSafe() : false;
}.property("isResponsive"),
@computed("isResponsive")
autoAdFormat(isResponsive) {
return isResponsive ? "auto".htmlSafe() : false;
},
adWrapperStyle: function() {
return (this.get("isResponsive")
? ""
: `width: ${this.get("ad_width")}; height: ${this.get("ad_height")};`
).htmlSafe();
}.property("ad_width", "ad_height"),
@computed("ad_width", "ad_height", "isResponsive")
adWrapperStyle(w, h, isResponsive) {
return (isResponsive ? "" : `width: ${w}; height: ${h};`).htmlSafe();
},
adInsStyle: function() {
@computed("adWrapperStyle", "isResponsive")
adInsStyle(adWrapperStyle, isResponsive) {
return `display: ${
this.get("isResponsive") ? "block" : "inline-block"
}; ${this.get("adWrapperStyle")}`.htmlSafe();
}.property("adWrapperStyle", "isResponsive"),
isResponsive ? "block" : "inline-block"
}; ${adWrapperStyle}`.htmlSafe();
},
checkTrustLevels: function() {
@computed("currentUser.trust_level")
showToTrustLevel(trustLevel) {
return !(
currentUser &&
currentUser.get("trust_level") >
Discourse.SiteSettings.adsense_through_trust_level
trustLevel && trustLevel > this.siteSettings.adsense_through_trust_level
);
}.property("trust_level"),
},
showAd: function() {
@computed("showToTrustLevel", "showToGroups", "showAfterPost")
showAd(showToTrustLevel, showToGroups, showAfterPost) {
return (
Discourse.SiteSettings.adsense_publisher_code &&
this.get("checkTrustLevels")
this.siteSettings.adsense_publisher_code &&
showToTrustLevel &&
showToGroups &&
showAfterPost
);
}.property("checkTrustLevels")
},
@computed("postNumber")
showAfterPost(postNumber) {
if (!postNumber) {
return true;
}
return this.isNthPost(parseInt(this.siteSettings.adsense_nth_post_code));
}
});
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,107 @@
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");
if (!houseAds || !houseAds.settings) {
return [];
}
const 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 === null) {
// 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}}

Some files were not shown because too many files have changed in this diff Show More