Commit fb3285e7 authored by ChrisSnyder's avatar ChrisSnyder
Browse files

Initial commit

parents
{
"presets": [
[
"env", {
"targets": {
"browsers": [
"last 2 versions",
"IE >= 9"
]
}
}
],
"stage-0",
"react"
]
}
\ No newline at end of file
assets/vendor/**/*
node_modules/**/*
**/js_test_files/**/*
*.js
!*.es6.js
modules/locale/tests/locale_test.es6.js
!tests/Drupal/Nightwatch/**/*.js
{
"extends": [
"airbnb",
"plugin:prettier/recommended"
],
"root": true,
"env": {
"browser": true,
"es6": true,
"node": true
},
"globals": {
"Drupal": true,
"DrupalGutenberg": true,
"wp": true,
"drupalSettings": true,
"drupalTranslations": true,
"domready": true,
"jQuery": true,
"_": true,
"matchMedia": true,
"Backbone": true,
"Modernizr": true,
"CKEDITOR": true,
"moment": true
},
"rules": {
"prettier/prettier": "error",
"consistent-return": ["off"],
"no-underscore-dangle": ["off"],
"max-nested-callbacks": ["warn", 3],
"import/no-mutable-exports": ["warn"],
"no-plusplus": ["warn", {
"allowForLoopAfterthoughts": true
}],
"no-param-reassign": ["off"],
"no-prototype-builtins": ["off"],
"valid-jsdoc": ["warn", {
"prefer": {
"returns": "return",
"property": "prop"
},
"requireReturn": false
}],
"no-unused-vars": ["warn"],
"react/react-in-jsx-scope": ["off"],
"react/jsx-filename-extension": ["off"],
"react/prop-types": ["off"],
"operator-linebreak": ["error", "after", { "overrides": { "?": "ignore", ":": "ignore" } }]
}
}
node_modules
\ No newline at end of file
{
"printWidth": 80,
"semi": true,
"singleQuote": true,
"trailingComma": "all"
}
{
"extends": "stylelint-config-standard",
"plugins": [
"stylelint-no-browser-hacks/lib"
],
"rules": {
"comment-empty-line-before": null,
"function-linear-gradient-no-nonstandard-direction": null,
"function-whitespace-after": null,
"no-descending-specificity": null,
"no-duplicate-selectors": null,
"no-unknown-animations": true,
"media-feature-name-no-unknown": [true, {
"ignoreMediaFeatureNames": ["prefers-reduced-motion"]
}],
"number-leading-zero": "always",
"plugin/no-browser-hacks": [true, {
"browsers": [
"ie >= 9",
"edge >= 13",
"firefox >= 5",
"opera >= 12",
"safari >= 5",
"chrome >= 56"
]
}],
"property-no-unknown": null,
"rule-empty-line-before": null,
"selector-pseudo-element-colon-notation": null,
"shorthand-property-no-redundant-values": null,
"unit-whitelist": ["deg", "em", "ex", "ms", "rem", "%", "s", "px", "vw", "vh"]
},
"ignoreFiles": [
"assets/vendor/**/*.css",
"tests/Drupal/Tests/Core/Asset/css_test_files/**/*.css"
]
}
Sitewide Alerts
---------------
This module provides you with the ability to show alerts at the top of your site.
This can be helpful for showing alerts to your visitors to inform them of planned matenice perods, shipping delays, flash sales. How you use it is up to you.
Features / Design Decisions
---------------------------
- Alerts are rendered at the top of the site, regardless of theme used. (no block configuration).
- Multiple alerts can be displayed at once.
- Alerts can have different styles. The number and type of styles are configurable. (For example you can create a very important alert that is red and a subtle one that is white.)
- Alerts can be optionally dismissed by visitors, so they are not notified again.
- Alerts can be sitewide or can be limited to a subset of pages.
- Alerts are loaded and displayed using a react component that will immediately display new alerts even if a visitor is already on the page.
- Alerts were designed to avoid breaking your full page an dynamic caches.
- Alerts can be scheduled to show and hide at specific times.
{
"name": "drupal/sitewide_alert",
"type": "drupal-module",
"license": "GPL-2.0+",
"description": "Provides ability to display an alert message at the top of all pages.",
"keywords": ["Drupal"],
"homepage": "https://www.drupal.org/project/sitewide_alert",
"minimum-stability": "dev",
"authors": [
{
"name": "Chris Snyder",
"email": "chris@chrissnyder.org",
"role": "Lead Developer"
}
],
"support": {
"issues": "https://www.drupal.org/project/issues/sitewide_alert",
"source": "http://cgit.drupalcode.org/sitewide_alert"
}
}
show_on_admin: 0
alert_styles: 'primary|Default'
.sitewide-alert__message {
/* Prevent the message from pushing down the close icon. */
display: inline-block;
}
// Sitewide Alert Component.
(function (Drupal) {
class SitewideAlert extends React.Component {
render() {
return (
<div className="sitewide-alert">
<span>HELLO CHRIS!</span>
</div>
);
}
}
})(Drupal);
/**
* DO NOT EDIT THIS FILE.
* See the following change record for more information,
* https://www.drupal.org/node/2815083
* @preserve
**/"use strict";
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
(function (Drupal) {
var SitewideAlert = function (_React$Component) {
_inherits(SitewideAlert, _React$Component);
function SitewideAlert() {
_classCallCheck(this, SitewideAlert);
return _possibleConstructorReturn(this, (SitewideAlert.__proto__ || Object.getPrototypeOf(SitewideAlert)).apply(this, arguments));
}
_createClass(SitewideAlert, [{
key: "render",
value: function render() {
return React.createElement(
"div",
{ className: "sitewide-alert" },
React.createElement(
"span",
null,
"HELLO CHRIS!"
)
);
}
}]);
return SitewideAlert;
}(React.Component);
})(Drupal);
\ No newline at end of file
// import React from 'react';
// import ReactDom from 'react-dom';
(function(Drupal) {
// window.sitewideAlert = sitewideAlert || {};
// const { SitewideAlert } = ./components/SitewideAlert.js;
class SitewideAlert extends React.Component {
constructor(props) {
super(props);
this.state = {
dismissed: this.alertWasDismissed(props.dismissalIgnoreBefore),
showOnThisPage: this.shouldShowOnThisPage(props.showOnPages, props.negateShowOnPages)
};
this.dismissAlert = this.dismissAlert.bind(this);
this.alertWasDismissed = this.alertWasDismissed.bind(this);
this.shouldShowOnThisPage = this.shouldShowOnThisPage.bind(this);
}
componentDidUpdate(prevProps) {
if (
this.props.dismissalIgnoreBefore !== prevProps.dismissalIgnoreBefore ||
this.props.showOnPages !== prevProps.showOnPages ||
this.props.negateShowOnPages !== prevProps.negateShowOnPages
) {
this.setState({
dismissed: this.alertWasDismissed(this.props.dismissalIgnoreBefore),
showOnThisPage: this.shouldShowOnThisPage(this.props.showOnPages, this.props.negateShowOnPages)
});
}
}
shouldShowOnThisPage(pages = [], negate = true) {
if (pages === []) {
return true;
}
let pagePathMatches = false;
const currentPath = window.location.pathname;
for (let i = 0; i < pages.length; i++) {
const page = pages[i];
// Check if we have to deal with a wild card.
if (page.charAt(page.length - 1) === '*') {
if (currentPath.startsWith(page.substring(0, page.length - 1))) {
pagePathMatches = true;
break;
}
} else if (page === currentPath) {
pagePathMatches = true;
break;
}
}
return negate ? !pagePathMatches : pagePathMatches;
}
alertWasDismissed(ignoreDismissalBefore) {
if (!('alert-dismissed-' + this.props.uuid in window.localStorage)) {
return false;
}
const dismissedAtTimestamp = Number(
window.localStorage.getItem('alert-dismissed-' + this.props.uuid),
);
// If the visitor has already dismissed the alert but we are supposed to ignore dismissals before a set date.
if (dismissedAtTimestamp < ignoreDismissalBefore) {
return false;
}
return true;
}
dismissAlert() {
window.localStorage.setItem('alert-dismissed-' + this.props.uuid, String(Math.round((new Date()).getTime() / 1000)));
this.setState({
dismissed: true,
showOnThisPage: this.state.showOnThisPage,
});
}
render() {
// Prevent the alert from showing if it has been dismissed.
if (this.props.dismissible && this.state.dismissed) {
return null;
}
if (!this.state.showOnThisPage) {
return null;
}
// Set alert classes.
let alertClasses = 'sitewide-alert alert';
if (this.props.styleClass !== '') {
alertClasses += ' ' + this.props.styleClass;
}
return (
<div className={alertClasses} role="alert">
{/* The inner HTML was already processed for XSS by Drupal's render method. */}
<span dangerouslySetInnerHTML={{__html: this.props.message}}/>
{this.props.dismissible && <button className="close" onClick={this.dismissAlert} aria-label="Close"><span aria-hidden="true">&times;</span></button>}
</div>
);
}
}
class SitewideAlerts extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
sitewideAlerts: [],
};
}
componentDidMount() {
this.getAlerts();
this.interval = setInterval(() => {
this.getAlerts();
}, 5000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
getAlerts() {
fetch(window.location.origin + '/sitewide_alert/load')
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
sitewideAlerts: result.sitewideAlerts,
});
},
// Note: it's important to handle errors here
// instead of a catch() block so that we don't swallow
// exceptions from actual bugs in components.
(error) => {
this.setState({
isLoaded: true,
error,
});
},
);
}
render() {
const { error, isLoaded, sitewideAlerts } = this.state;
if (error) {
console.log('Unable to to load alerts.');
return <div/>;
} else if (!isLoaded) {
return <div/>;
} else {
return (
<div>
{sitewideAlerts.map(sitewideAlert => (
<SitewideAlert
key={sitewideAlert.uuid}
uuid={sitewideAlert.uuid}
message={sitewideAlert.message}
styleClass={sitewideAlert.styleClass}
dismissible={sitewideAlert.dismissible}
dismissalIgnoreBefore={sitewideAlert.dismissalIgnoreBefore}
showOnPages={sitewideAlert.showOnPages}
negateShowOnPages={sitewideAlert.negateShowOnPages}
/>
))}
</div>
);
}
}
}
Drupal.behaviors.sitewide_alert_init = {
attach: context => {
ReactDOM.render(
<SitewideAlerts />,
document.getElementById('sitewide-alert'),
);
},
};
})(Drupal);
/**
* DO NOT EDIT THIS FILE.
* See the following change record for more information,
* https://www.drupal.org/node/2815083
* @preserve
**/'use strict';
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
(function (Drupal) {
var SitewideAlert = function (_React$Component) {
_inherits(SitewideAlert, _React$Component);
function SitewideAlert(props) {
_classCallCheck(this, SitewideAlert);
var _this = _possibleConstructorReturn(this, (SitewideAlert.__proto__ || Object.getPrototypeOf(SitewideAlert)).call(this, props));
_this.state = {
dismissed: _this.alertWasDismissed(props.dismissalIgnoreBefore),
showOnThisPage: _this.shouldShowOnThisPage(props.showOnPages, props.negateShowOnPages)
};
_this.dismissAlert = _this.dismissAlert.bind(_this);
_this.alertWasDismissed = _this.alertWasDismissed.bind(_this);
_this.shouldShowOnThisPage = _this.shouldShowOnThisPage.bind(_this);
return _this;
}
_createClass(SitewideAlert, [{
key: 'componentDidUpdate',
value: function componentDidUpdate(prevProps) {
if (this.props.dismissalIgnoreBefore !== prevProps.dismissalIgnoreBefore || this.props.showOnPages !== prevProps.showOnPages || this.props.negateShowOnPages !== prevProps.negateShowOnPages) {
this.setState({
dismissed: this.alertWasDismissed(this.props.dismissalIgnoreBefore),
showOnThisPage: this.shouldShowOnThisPage(this.props.showOnPages, this.props.negateShowOnPages)
});
}
}
}, {
key: 'shouldShowOnThisPage',
value: function shouldShowOnThisPage() {
var pages = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
var negate = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
if (pages === []) {
return true;
}
var pagePathMatches = false;
var currentPath = window.location.pathname;
for (var i = 0; i < pages.length; i++) {
var page = pages[i];
if (page.charAt(page.length - 1) === '*') {
if (currentPath.startsWith(page.substring(0, page.length - 1))) {
pagePathMatches = true;
break;
}
} else if (page === currentPath) {
pagePathMatches = true;
break;
}
}
return negate ? !pagePathMatches : pagePathMatches;
}
}, {
key: 'alertWasDismissed',
value: function alertWasDismissed(ignoreDismissalBefore) {
if (!('alert-dismissed-' + this.props.uuid in window.localStorage)) {
return false;
}
var dismissedAtTimestamp = Number(window.localStorage.getItem('alert-dismissed-' + this.props.uuid));
if (dismissedAtTimestamp < ignoreDismissalBefore) {
return false;
}
return true;
}
}, {
key: 'dismissAlert',
value: function dismissAlert() {
window.localStorage.setItem('alert-dismissed-' + this.props.uuid, String(Math.round(new Date().getTime() / 1000)));
this.setState({
dismissed: true,
showOnThisPage: this.state.showOnThisPage
});
}
}, {
key: 'render',
value: function render() {
if (this.props.dismissible && this.state.dismissed) {
return null;
}
if (!this.state.showOnThisPage) {
return null;
}
var alertClasses = 'sitewide-alert alert';
if (this.props.styleClass !== '') {
alertClasses += ' ' + this.props.styleClass;
}
return React.createElement(
'div',
{ className: alertClasses, role: 'alert' },
React.createElement('span', { dangerouslySetInnerHTML: { __html: this.props.message } }),
this.props.dismissible && React.createElement(
'button',
{ className: 'close', onClick: this.dismissAlert, 'aria-label': 'Close' },
React.createElement(
'span',
{ 'aria-hidden': 'true' },
'\xD7'
)
)
);
}
}]);
return SitewideAlert;
}(React.Component);
var SitewideAlerts = function (_React$Component2) {
_inherits(SitewideAlerts, _React$Component2);
function SitewideAlerts(props) {
_classCallCheck(this, SitewideAlerts);
var _this2 = _possibleConstructorReturn(this, (SitewideAlerts.__proto__ || Object.getPrototypeOf(SitewideAlerts)).call(this, props));
_this2.state = {
error: null,
isLoaded: false,
sitewideAlerts: []
};
return _this2;
}
_createClass(SitewideAlerts, [{
key: 'componentDidMount',
value: function componentDidMount() {
var _this3 = this;
this.getAlerts();
this.interval = setInterval(function () {
_this3.getAlerts();
}, 5000);
}
}, {
key: 'componentWillUnmount',
value: function componentWillUnmount() {
clearInterval(this.interval);
}
}, {
key: 'getAlerts',
value: function getAlerts() {
var _this4 = this;
fetch(window.location.origin + '/sitewide_alert/load').then(function (res) {
return res.json();
}).then(function (result) {
_this4.setState({
isLoaded: true,
sitewideAlerts: result.sitewideAlerts
});
}, function (error) {
_this4.setState({
isLoaded: true,
error: error
});
});
}
}, {
key: 'render',