Added mobile friendly ads & ads (#161)

* Fix spacing issues on mobile

* Added back linting on modules directory.
Please don't look at the dirty fixes :x

* Add support for responsive ads.

* Add lazy loading of images.
This commit is contained in:
Redblueflame
2021-04-15 15:48:33 +02:00
committed by GitHub
parent 28092d6862
commit 0bda636113
15 changed files with 289 additions and 229 deletions

View File

@@ -1,6 +1,6 @@
export const DEFAULT_OPTIONS = {
enabled: false,
script_url: 'https://example.com',
tracking_code: 'xxx'
enabled: false,
script_url: 'https://example.com',
tracking_code: 'xxx',
}
export const UNAMI_LIB_TAG_ID = 'unami-import'
export const UNAMI_LIB_TAG_ID = 'unami-import'

View File

@@ -1,22 +1,24 @@
import {
DEFAULT_OPTIONS,
UNAMI_LIB_TAG_ID
} from './constants';
import { DEFAULT_OPTIONS, UNAMI_LIB_TAG_ID } from './constants'
const { resolve } = require('path');
const { resolve } = require('path')
// eslint-disable-next-line require-await
module.exports = async function module(moduleOptions) {
const options = Object.assign(DEFAULT_OPTIONS, this.options.analytics, moduleOptions);
const options = Object.assign(
DEFAULT_OPTIONS,
this.options.analytics,
moduleOptions
)
const templatesOptions = {
...options,
UNAMI_LIB_TAG_ID
};
this.addPlugin({
src: resolve(__dirname, 'templates/plugin.js'),
fileName: 'analytics/plugin.js',
options: templatesOptions,
});
};
module.exports.meta = require('../package.json');
const templatesOptions = {
...options,
UNAMI_LIB_TAG_ID,
}
this.addPlugin({
src: resolve(__dirname, 'templates/plugin.js'),
fileName: 'analytics/plugin.js',
options: templatesOptions,
})
}
module.exports.meta = require('../package.json')

View File

@@ -1,64 +1,35 @@
import Vue from 'vue';
function isAnalyticsOn(ctx) {
let cookies = null
if (ctx.req != null) {
//Server side rendering
cookies = ctx.req.headers.cookie;
} else {
// Rely on the client
cookies = document.cookie;
}
if (cookies == null) return false
let processed = {}
cookies.split(';').forEach((e) => {
let val = e.trim().split('=');
processed[val[0]] = decodeURI(val[1]);
})
let scopes = decodeURIComponent(processed['modrinth-scopes']).split(",");
return (scopes !== null && scopes.includes('analytics'));
}
// eslint-disable-next-line require-await
export default async function (ctx, inject) {
const config = (ctx.$config && ctx.$config.analytics) || {}
const { app } = ctx;
const config = ctx.$config && ctx.$config.analytics || {};
const url = config.script_url ?? '<%= options.script_url %>';
const tag = config.tracking_code ?? '<%= options.tracking_code %>';
const enabled = config.enabled ?? <%= options.enabled || false %>;
const url = config.script_url ?? '<%= options.script_url %>'
const tag = config.tracking_code ?? '<%= options.tracking_code %>'
// eslint-disable-next-line
const enabled = config.enabled ?? ('<%= options.enabled || false %>' === 'true');
// Check if the parameters are not changed by runtime config:
const UNAMI_LIB_TAG_ID = '<%= options.UNAMI_LIB_TAG_ID %>';
const UNAMI_LIB_TAG_ID = '<%= options.UNAMI_LIB_TAG_ID %>'
if (!enabled) {
return;
return
}
const injectScript = (script) => {
const scriptIndex = ctx.app.head.script.findIndex(s => s.hid === script.hid);
const scriptIndex = ctx.app.head.script.findIndex(
(s) => s.hid === script.hid
)
if (scriptIndex !== -1) {
ctx.app.head.script[scriptIndex] = script;
ctx.app.head.script[scriptIndex] = script
} else {
ctx.app.head.script.push(script);
ctx.app.head.script.push(script)
}
};
// if (isAnalyticsOn(ctx)) {
// Inject unami
const analyticsScript = {
hid: UNAMI_LIB_TAG_ID,
src: url,
'data-website-id': 'c37613de-245d-4767-90e7-ba7980a4f1a2',
async: true,
defer: true,
};
injectScript(analyticsScript);
// } else {
// console.log("Analytics scope was denied.")
// }
}
const analyticsScript = {
hid: UNAMI_LIB_TAG_ID,
src: url,
'data-website-id': tag,
async: true,
defer: true,
}
injectScript(analyticsScript)
}

View File

@@ -7,10 +7,10 @@ export const DEFAULT_OPTIONS = {
responsive: false,
collapseEmptyDivs: false,
emptyClass: 'is-empty',
geoEdgeId: ''
};
geoEdgeId: '',
}
export const GPT_LIB_SCRIPT_ID = 'google-publisher-tag-lib-script';
export const GPT_INIT_SCRIPT_ID = 'google-publisher-tag-init-script';
export const GEOEDGE_CONF_SCRIPT_ID = 'geoedge-config-script';
export const GEOEDGE_LIB_SCRIPT_ID = 'geoedge-lib-script';
export const GPT_LIB_SCRIPT_ID = 'google-publisher-tag-lib-script'
export const GPT_INIT_SCRIPT_ID = 'google-publisher-tag-init-script'
export const GEOEDGE_CONF_SCRIPT_ID = 'geoedge-config-script'
export const GEOEDGE_LIB_SCRIPT_ID = 'geoedge-lib-script'

View File

@@ -4,12 +4,17 @@ import {
GPT_INIT_SCRIPT_ID,
GEOEDGE_CONF_SCRIPT_ID,
GEOEDGE_LIB_SCRIPT_ID,
} from './constants';
} from './constants'
const { resolve } = require('path');
const { resolve } = require('path')
// eslint-disable-next-line require-await
module.exports = async function module(moduleOptions) {
const options = Object.assign(DEFAULT_OPTIONS, this.options.ads, moduleOptions);
const options = Object.assign(
DEFAULT_OPTIONS,
this.options.ads,
moduleOptions
)
const templatesOptions = {
...options,
@@ -17,18 +22,18 @@ module.exports = async function module(moduleOptions) {
GPT_INIT_SCRIPT_ID,
GEOEDGE_CONF_SCRIPT_ID,
GEOEDGE_LIB_SCRIPT_ID,
};
}
this.addPlugin({
src: resolve(__dirname, 'templates/plugin.js'),
fileName: 'gpt-ads-module/plugin.js',
options: templatesOptions,
});
})
this.addTemplate({
src: resolve(__dirname, 'templates/component.js'),
fileName: 'gpt-ads-module/component.js',
options: templatesOptions,
});
};
module.exports.meta = require('../package.json');
})
}
module.exports.meta = require('../package.json')

View File

@@ -1,3 +1,4 @@
/* eslint-disable no-undef */
export default {
name: '<%= options.componentName %>',
data: () => ({
@@ -29,7 +30,7 @@ export default {
isResponsive: {
type: Boolean,
required: false,
default: <%= options.responsive %>,
default: '<%= options.responsive %>' === 'true',
},
windowResizeDebounce: {
type: Number,
@@ -44,40 +45,42 @@ export default {
},
computed: {
ghostMode() {
return this.$config.ads.ghostMode ?? <%= options.ghostMode %>;
return this.$config.ads.ghostMode ?? '<%= options.ghostMode %>' === true
},
networkCode() {
const { $gptAds } = this;
return $gptAds ? $gptAds.networkCode : null;
const { $gptAds } = this
return $gptAds ? $gptAds.networkCode : null
},
adUnitPath() {
const { networkCode, adUnit } = this;
return `/${networkCode}/${adUnit}`;
const { networkCode, adUnit } = this
return `/${networkCode}/${adUnit}`
},
divId() {
const { id } = this;
return `div-gpt-ad-${id}-0`;
const { id } = this
return `div-gpt-ad-${id}-0`
},
formattedSize() {
return this.formatSizeList(this.size);
return this.formatSizeList(this.size)
},
style() {
if (this.ghostMode) {
const { formattedSize, currentSizeMappingIndex, mapping } = this;
let baseSize = formattedSize;
const { formattedSize, currentSizeMappingIndex, mapping } = this
let baseSize = formattedSize
if (currentSizeMappingIndex !== null) {
baseSize = mapping[currentSizeMappingIndex][1];
baseSize = mapping[currentSizeMappingIndex][1]
}
const size = Array.isArray(baseSize[0]) ? baseSize[0] : [baseSize[0], baseSize[1]];
const [width, height] = size;
const size = Array.isArray(baseSize[0])
? baseSize[0]
: [baseSize[0], baseSize[1]]
const [width, height] = size
return {
margin: '0 auto',
width: `${width}px`,
height: `${height}px`,
border: '1px solid black',
};
}
}
return null;
return null
},
},
methods: {
@@ -91,12 +94,12 @@ export default {
*/
formatSize(size) {
if (Array.isArray(size)) {
return size;
return size
}
if (typeof size === 'string') {
return size.split('x').map(value => parseInt(value, 10));
return size.split('x').map((value) => parseInt(value, 10))
}
return [];
return []
},
/**
* Formats a given list of sizes to make it compatible with GPT API
@@ -109,26 +112,25 @@ export default {
*/
formatSizeList(sizesList) {
if (Array.isArray(sizesList)) {
return sizesList;
return sizesList
}
if (typeof sizesList === 'string') {
return sizesList
.split(',')
.map(size => this.formatSize(size));
return sizesList.split(',').map((size) => this.formatSize(size))
}
return [];
return []
},
/**
* Refresh ad slot
*/
refreshSlot() {
googletag.pubads().refresh([this.adSlot]);
console.log('Refreshing slot.')
googletag.pubads().refresh([this.adSlot])
},
handleSlotRenderEnded (event) {
handleSlotRenderEnded(event) {
if (event.slot.getSlotId().getDomId() !== this.divId) {
return;
return
}
this.isEmpty = !!event.isEmpty;
this.isEmpty = !!event.isEmpty
},
/**
* Window resize event listener
@@ -137,17 +139,17 @@ export default {
* the case
*/
handleWindowResize() {
const { windowResizeDebounce } = this;
clearTimeout(this.windowResizeListenerDebounce);
const { windowResizeDebounce } = this
clearTimeout(this.windowResizeListenerDebounce)
this.windowResizeListenerDebounce = setTimeout(() => {
const currentSizeMappingIndex = this.getCurrentSizeMappingIndex();
const currentSizeMappingIndex = this.getCurrentSizeMappingIndex()
if (currentSizeMappingIndex !== this.currentSizeMappingIndex) {
if (!this.ghostMode) {
this.refreshSlot();
this.refreshSlot()
}
this.currentSizeMappingIndex = currentSizeMappingIndex;
this.currentSizeMappingIndex = currentSizeMappingIndex
}
}, windowResizeDebounce);
}, windowResizeDebounce)
},
/**
* Gets the current size mapping index
@@ -155,94 +157,96 @@ export default {
* @return {Number} The current size mapping index
*/
getCurrentSizeMappingIndex() {
const mapping = this.mapping || [];
let index = null;
const mapping = this.mapping || []
let index = null
mapping.some((size, i) => {
const [browserSize] = size;
const [width, height] = browserSize;
const mediaQuery = `(min-width: ${width}px) and (min-height: ${height}px)`;
const [browserSize] = size
const [width, height] = browserSize
const mediaQuery = `(min-width: ${width}px) and (min-height: ${height}px)`
if (window.matchMedia(mediaQuery).matches) {
index = i;
return true;
index = i
return true
}
return false;
});
return index;
return false
})
return index
},
},
mounted() {
if (!window.googletag) {
return;
return
}
const {
ghostMode,
adUnitPath,
divId,
sizeMapping,
isResponsive,
collapseEmptyDiv,
} = this;
} = this
// Init Ad slot
googletag.cmd.push(() => {
const pubadsService = googletag.pubads()
pubadsService.addEventListener('slotRenderEnded', this.handleSlotRenderEnded);
pubadsService.setTargeting('path', this.$route.path);
pubadsService.addEventListener(
'slotRenderEnded',
this.handleSlotRenderEnded
)
pubadsService.setTargeting('path', this.$route.path)
const adSlot = googletag
.defineSlot(adUnitPath, this.formattedSize, divId)
.addService(pubadsService);
.addService(pubadsService)
// Collapse empty div slot-level override
if (collapseEmptyDiv !== null) {
adSlot.setCollapseEmptyDiv(collapseEmptyDiv);
adSlot.setCollapseEmptyDiv(collapseEmptyDiv)
}
// Build size mapping if any
if (sizeMapping.length > 0) {
const mapping = googletag.sizeMapping();
const mapping = googletag.sizeMapping()
sizeMapping.forEach((size) => {
const browserSize = this.formatSize(size[0]);
const adSizes = this.formatSizeList(size[1]);
mapping.addSize(browserSize, adSizes);
this.mapping.push([browserSize, adSizes]);
});
adSlot.defineSizeMapping(mapping.build());
const browserSize = this.formatSize(size[0])
const adSizes = this.formatSizeList(size[1])
mapping.addSize(browserSize, adSizes)
this.mapping.push([browserSize, adSizes])
})
adSlot.defineSizeMapping(mapping.build())
}
// Init responsive behavior
if (this.sizeMapping.length > 0 && isResponsive) {
const currentSizeMappingIndex = this.getCurrentSizeMappingIndex();
this.currentSizeMappingIndex = currentSizeMappingIndex;
window.addEventListener('resize', this.handleWindowResize);
const currentSizeMappingIndex = this.getCurrentSizeMappingIndex()
this.currentSizeMappingIndex = currentSizeMappingIndex
window.addEventListener('resize', this.handleWindowResize)
}
this.adSlot = adSlot;
this.$gptAds.slots.push(adSlot);
this.adSlot = adSlot
this.$gptAds.slots.push(adSlot)
if (!this.ghostMode) {
googletag.display(divId);
googletag.display(divId)
if (this.$gptAds.individualRefresh) {
this.refreshSlot();
this.refreshSlot()
}
}
});
})
},
beforeDestroy() {
console.log('Destroying ad.')
if (!googletag) {
return;
return
}
// Destroy ad slot
googletag.cmd.push(() => {
const destroyed = googletag.destroySlots([this.adSlot]);
});
googletag.destroySlots([this.adSlot])
})
// Remove window resize listener
window.removeEventListener('resize', this.handleWindowResize);
window.removeEventListener('resize', this.handleWindowResize)
},
render(h) {
const { divId, style, isEmpty } = this;
let classAttr = isEmpty ? '<%= options.emptyClass %>' : '';
const { divId, style, isEmpty } = this
const classAttr = isEmpty ? '<%= options.emptyClass %>' : ''
return h('div', {
style,
@@ -251,6 +255,6 @@ export default {
class: classAttr,
},
domProps: { innerHTML: '' },
});
})
},
};
}

View File

@@ -1,71 +1,80 @@
import Vue from 'vue';
import Vue from 'vue'
function isPersonalizedAdsOn(ctx) {
let cookies = []
if (ctx.req != null) {
//Server side rendering
cookies = ctx.req.headers.cookie;
// Server side rendering
cookies = ctx.req.headers.cookie
} else {
// Rely on the client
cookies = document.cookie;
cookies = document.cookie
}
if (cookies == null) return false
let processed = {}
const processed = {}
cookies.split(';').forEach((e) => {
let val = e.trim().split('=')
const val = e.trim().split('=')
processed[val[0]] = decodeURI(val[1])
})
let scopes = decodeURIComponent(processed['modrinth-scopes']).split(",")
return (scopes !== null && scopes.includes('ads'))
const scopes = decodeURIComponent(processed['modrinth-scopes']).split(',')
return scopes !== null && scopes.includes('ads')
}
// eslint-disable-next-line require-await
export default async function (ctx, inject) {
const { app } = ctx;
const config = ctx.$config && ctx.$config.ads || {};
const config = (ctx.$config && ctx.$config.ads) || {}
// Module options
const debug = config.debug ?? <%= options.debug || false %>;
const individualRefresh = config.individualRefresh ?? <%= options.individualRefresh || false %>;
const collapseEmptyDivs = config.collapseEmptyDivs ?? <%= options.collapseEmptyDivs || false %>;
const GeoEdgeId = config.GeoEdgeId ?? '<%= options.geoEdgeId %>';
const networkCode = config.networkCode ?? '<%= options.networkCode %>';
const GPT_LIB_SCRIPT_ID = '<%= options.GPT_LIB_SCRIPT_ID %>';
const GPT_INIT_SCRIPT_ID = '<%= options.GPT_INIT_SCRIPT_ID %>';
const GEOEDGE_CONF_SCRIPT_ID = '<%= options.GEOEDGE_CONF_SCRIPT_ID %>';
const GEOEDGE_LIB_SCRIPT_ID = '<%= options.GEOEDGE_LIB_SCRIPT_ID %>';
const debug = config.debug ?? '<%= options.debug || false %>' === 'true'
const individualRefresh =
config.individualRefresh ??
'<%= options.individualRefresh || false %>' === 'true'
const collapseEmptyDivs =
config.collapseEmptyDivs ??
'<%= options.collapseEmptyDivs || false %>' === 'true'
const GeoEdgeId = config.GeoEdgeId ?? '<%= options.geoEdgeId %>'
const networkCode = config.networkCode ?? '<%= options.networkCode %>'
const GPT_LIB_SCRIPT_ID = '<%= options.GPT_LIB_SCRIPT_ID %>'
const GPT_INIT_SCRIPT_ID = '<%= options.GPT_INIT_SCRIPT_ID %>'
const GEOEDGE_CONF_SCRIPT_ID = '<%= options.GEOEDGE_CONF_SCRIPT_ID %>'
const GEOEDGE_LIB_SCRIPT_ID = '<%= options.GEOEDGE_LIB_SCRIPT_ID %>'
// Instance options
const gptAdsOptions = {
networkCode,
individualRefresh,
slots: [],
};
}
const injectScript = (script) => {
const scriptIndex = ctx.app.head.script.findIndex(s => s.hid === script.hid);
const scriptIndex = ctx.app.head.script.findIndex(
(s) => s.hid === script.hid
)
if (scriptIndex !== -1) {
ctx.app.head.script[scriptIndex] = script;
ctx.app.head.script[scriptIndex] = script
} else {
ctx.app.head.script.push(script);
ctx.app.head.script.push(script)
}
};
let no_consent = !isPersonalizedAdsOn(ctx)
}
const noConsent = !isPersonalizedAdsOn(ctx)
// GeoEdge support
if (GeoEdgeId !== '') {
// Unfortunately these lines are needed to prevent vue-meta from esacping quotes in the init script
ctx.app.head.__dangerouslyDisableSanitizersByTagID = ctx.app.head.__dangerouslyDisableSanitizersByTagID || {}
ctx.app.head.__dangerouslyDisableSanitizersByTagID[GEOEDGE_CONF_SCRIPT_ID] = ['innerHTML']
ctx.app.head.__dangerouslyDisableSanitizersByTagID =
ctx.app.head.__dangerouslyDisableSanitizersByTagID || {}
ctx.app.head.__dangerouslyDisableSanitizersByTagID[
GEOEDGE_CONF_SCRIPT_ID
] = ['innerHTML']
const geoEdgeConfig = {
hid: GEOEDGE_CONF_SCRIPT_ID,
innerHTML: "window.grumi = { key: '" + encodeURIComponent(GeoEdgeId) +"'};"
};
injectScript(geoEdgeConfig);
innerHTML:
"window.grumi = { key: '" + encodeURIComponent(GeoEdgeId) + "'};",
}
injectScript(geoEdgeConfig)
const geoEdgeImport = {
hid: GEOEDGE_LIB_SCRIPT_ID,
src: `https://rumcdn.geoedge.be/${GeoEdgeId}/grumi-ip.js`,
async: true,
};
}
injectScript(geoEdgeImport)
}
@@ -74,20 +83,28 @@ export default async function (ctx, inject) {
hid: GPT_LIB_SCRIPT_ID,
src: 'https://www.googletagservices.com/tag/js/gpt.js',
async: true,
};
injectScript(gptLibScript);
}
injectScript(gptLibScript)
// Inject GPT init script
let gptInitScriptHtml = 'var googletag = googletag || {};googletag.cmd = googletag.cmd || [];';
let gptInitScriptHtml =
'var googletag = googletag || {};googletag.cmd = googletag.cmd || [];'
if (debug) {
gptInitScriptHtml += 'googletag.cmd.push(function(){googletag.openConsole();});';
gptInitScriptHtml +=
'googletag.cmd.push(function(){googletag.openConsole();});'
}
// Disable initial load
const gptDisableInitialLoad = individualRefresh ? 'googletag.pubads().disableInitialLoad();' : '';
const gptDisableInitialLoad = individualRefresh
? 'googletag.pubads().disableInitialLoad();'
: ''
// Collapse empty div
const gptCollapseEmptyDivs = collapseEmptyDivs ? 'googletag.pubads().collapseEmptyDivs();' : '';
const gptCollapseEmptyDivs = collapseEmptyDivs
? 'googletag.pubads().collapseEmptyDivs();'
: ''
// Desactivate personalization
const gptDisablePersonalization = no_consent ? 'googletag.pubads().setRequestNonPersonalizedAds(1);' : '';
const gptDisablePersonalization = noConsent
? 'googletag.pubads().setRequestNonPersonalizedAds(1);'
: ''
gptInitScriptHtml += `
googletag.cmd.push(function(){
googletag.pubads().enableSingleRequest();
@@ -96,17 +113,15 @@ export default async function (ctx, inject) {
${gptDisablePersonalization}
googletag.enableServices();
});
`;
`
const gptInitScript = {
hid: GPT_INIT_SCRIPT_ID,
innerHTML: gptInitScriptHtml,
};
injectScript(gptInitScript);
}
injectScript(gptInitScript)
const component = require('./component.js')
Vue.component('<%= options.componentName %>', component.default || component)
const component = require('./component.js');
Vue.component('<%= options.componentName %>', component.default || component);
inject('gptAds', gptAdsOptions);
inject('gptAds', gptAdsOptions)
}