diff --git a/Gemfile.lock b/Gemfile.lock index df9f32601..78e343303 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - uswds-jekyll (5.2.0) + uswds-jekyll (5.3.0) jekyll (>= 4.0, < 5) jekyll-autoprefixer mini_racer @@ -88,4 +88,4 @@ DEPENDENCIES uswds-jekyll! BUNDLED WITH - 2.1.4 + 2.2.5 diff --git a/README.md b/README.md index 74c39e2ca..0ce55f6ef 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ This is a [Jekyll theme](https://jekyllrb.com/docs/themes/) for the - [Analytics](#analytics) - [Last modified date](#last-modified-date) - [Anchor JS](#anchor-js) + - [Private Eye JS](#private-eye-js) 1. [Assets](#assets) - [Stylesheets](#stylesheets) - [Scripts](#scripts) @@ -311,6 +312,14 @@ You can show an anchor link next to header tags by uncommenting this section fro # anchor_js_targets: [h1, h2, h3, h4, h5, h6] ``` +### Private Eye JS + +By default, the USWDS Jekyll theme uses [Private Eye](https://github.com/18F/private-eye) to denote private links. You can turn this off with the setting below. If you would like to customize the default Private Eye configuration, you can find it in `/assets/js/private_eye_conf.js`. + +```yml +private_eye: false +``` + ## Assets The [stylesheet](_includes/styles.html) and [script](_includes/scripts.html) includes each incorporate the USWDS CSS and JS files if the corresponding `styles` and `scripts` lists aren't defined in your `_config.yml`. So unless you add one or both of those manually, your HTML will include the following: diff --git a/_config.yml b/_config.yml index d4885a06a..bab0451a1 100644 --- a/_config.yml +++ b/_config.yml @@ -23,6 +23,11 @@ github_info: # See https://github.com/bryanbraun/anchorjs for more information. # anchor_js_targets: [h1, h2, h3, h4, h5, h6] +# Enables Private Eye functionality. +# See https://github.com/18F/private-eye for more information. +# Private Eye configuration is in assets/js/main.js +private_eye: true + # To enable search, uncomment the search section # You will need to setup a search account with search.gov # https://search.usa.gov/signup diff --git a/_includes/scripts.html b/_includes/scripts.html index f224ff490..6bf2b52fb 100644 --- a/_includes/scripts.html +++ b/_includes/scripts.html @@ -1,18 +1,25 @@ + {% assign _scripts = '' | split: '' %} {% assign _async_marker = 'uswds_async=true' %} {% assign _site_scripts = site.scripts %} {% unless _site_scripts -%} {% assign _uswds_js = '/assets/uswds/js/uswds.min.js' | append: '?' | append: _async_marker %} - {% assign _site_scripts = '' | split: '' - | push: _uswds_js %} - {% if site.anchor_js_targets %} - {% assign _anchor_js = '/assets/js/vendor/anchor.min.js' %} - {% assign _main_js = '/assets/js/main.js' %} - {% assign _site_scripts = _site_scripts - | push: _anchor_js - | push: _main_js %} - {% endif %} + {% assign _site_scripts = '' | split: '' | push: _uswds_js %} + {% if site.anchor_js_targets %} + {% assign _anchor_js = '/assets/js/vendor/anchor.min.js' %} + {% assign _anchor_conf_js = '/assets/js/anchor.js' %} + {% assign _site_scripts = _site_scripts + | push: _anchor_js + | push: _main_js %} + {% endif %} {% endunless %} +{% if site.private_eye %} + {% assign _pi_js = '/assets/js/vendor/private_eye.js' %} + {% assign _pi_conf_js = '/assets/js/private_eye_conf.js' %} + {% assign _site_scripts = _site_scripts + | push: _pi_js + | push: _pi_conf_js %} +{% endif %} {% assign _scripts = _scripts | push: _site_scripts | push: layout.scripts diff --git a/assets/js/main.js b/assets/js/anchor.js similarity index 100% rename from assets/js/main.js rename to assets/js/anchor.js diff --git a/assets/js/private_eye_conf.js b/assets/js/private_eye_conf.js new file mode 100644 index 000000000..ee010e2ad --- /dev/null +++ b/assets/js/private_eye_conf.js @@ -0,0 +1,39 @@ + +document.addEventListener('DOMContentLoaded', function() { + PrivateEye({ + defaultMessage: "This link is private to TTS.", + ignoreUrls: [ + '18f.slack.com', + 'anywhere.gsa.gov', + 'bookit.gsa.gov', + 'calendar.gsa.gov', + 'connect.gsa.gov', + 'docs.google.com', + 'drive.google.com', + 'ea.gsa.gov', + 'email.gsa.gov', + 'eopf.opm.gov', + 'gcims.gsa.gov', + 'github.com/18F/Accessibility_Reviews', + 'github.com/18F/blog-drafts', + 'github.com/18F/codereviews', + 'github.com/18F/DevOps', + 'github.com/18F/Infrastructure', + 'github.com/18F/security-incidents', + 'github.com/18F/staffing', + 'github.com/18F/team-api.18f.gov', + 'github.com/18F/writing-lab', + 'gkey.gsa.gov', + 'gsa-tts.slack.com', + 'gsa.my.salesforce.com', + 'gsaolu.gsa.gov', + 'hrlinks.gsa.gov', + 'hrprod.hr.gsa.gov', + 'insite.gsa.gov', + 'mail.gsa.gov', + 'meet.gsa.gov', + 'sign.gsa.gov', + 'tock.18f.gov' + ] + }); + }, false ); \ No newline at end of file diff --git a/assets/js/vendor/private_eye.js b/assets/js/vendor/private_eye.js new file mode 100644 index 000000000..1c1145c66 --- /dev/null +++ b/assets/js/vendor/private_eye.js @@ -0,0 +1,106 @@ +// https://github.com/18F/private-eye +(function() { + 'use strict'; + + // The line below differs from private eye v2.0. We need to update the source file. + var STYLES = 'a.private-link::after { content: "\\1F512"; font-size: 0.75em; text-decoration: none }'; + var STYLES_ID = '_privateEye-styles'; + + var DEFAULT_OPTIONS = { + defaultMessage: 'This is a link to a private site, which may or may not be accessible to you.', + wrapper: '' + }; + + var isString = function(str) { return !!str && typeof str === 'string'; }; + var isArray = function(arr) { return !!arr && arr.length; }; + + var optionValidators = { + defaultMessage: isString, + wrapper: isString, + ignoreUrls: isArray, + }; + + function setStyles() { + var styles = document.createElement('style'); + styles.innerHTML = STYLES; + styles.id = STYLES_ID; + document.body.appendChild(styles); + } + + function getOptions(opts) { + var newObj = {}; + + for (var prop in DEFAULT_OPTIONS) { + newObj[prop] = DEFAULT_OPTIONS[prop]; + } + + for (var prop in opts) { + var val = opts[prop]; + + if (optionValidators[prop](val)) { + newObj[prop] = val; + } + } + + return newObj; + } + + var PrivateEye = function(opts) { + // The old docs recommend calling this as a function. This is here to detect + // those cases and make sure backward compatibility stays intact now that the + // new syntax is preferred. + if (!(this instanceof PrivateEye)) { + return new PrivateEye(opts); + } + + // Don't add the styles to the page more than once. + if (!document.getElementById(STYLES_ID)) { + setStyles(); + } + + this.opts = getOptions(opts); + + this.checkLinks(); + }; + + PrivateEye.prototype.checkLinks = function() { + var self = this; + + this.opts.ignoreUrls.forEach(function(url) { + var hrefValue; + var titleValue; + + // If the `url` is an Object, then parse the properties `message` & `url` + if (url === Object(url)) { + titleValue = url.message; + hrefValue = url.url; + } else { + hrefValue = url; + titleValue = self.opts.defaultMessage; + } + + var wrapper = self.opts.wrapper.length ? self.opts.wrapper + ' ' : ''; + var selector = wrapper + 'a'; + var anchors = document.querySelectorAll(selector); + + Array.prototype.forEach.call(anchors, function(anchor) { + var anchorHref = anchor.href.toLowerCase().trim(); + + if (anchorHref.indexOf(hrefValue.toLowerCase()) !== -1) { + anchor.className += ' private-link'; + + // Only replace the anchor's title if it is empty + if (!anchor.title) { + anchor.title = titleValue; + } + } + }); + }); + } + + if (typeof module === 'object' && typeof module.exports === 'object') { + module.exports = PrivateEye; + } else { + window.PrivateEye = PrivateEye; + } + })(); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index f619a0b23..0a115df5c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3,26 +3,31 @@ "requires": true, "lockfileVersion": 1, "dependencies": { + "@18f/private-eye": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@18f/private-eye/-/private-eye-2.0.0.tgz", + "integrity": "sha512-2ZiJSw/frar1pvp+wvRMyvFepVhulN08An9T+y1A8Psfssb72RPkwV9DLM56LO8mEMy/qV/d8e/eAZj1MiYPqA==" + }, "@nodelib/fs.scandir": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", - "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", + "integrity": "sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA==", "requires": { - "@nodelib/fs.stat": "2.0.3", + "@nodelib/fs.stat": "2.0.4", "run-parallel": "^1.1.9" } }, "@nodelib/fs.stat": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", - "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==" + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz", + "integrity": "sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q==" }, "@nodelib/fs.walk": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", - "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz", + "integrity": "sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow==", "requires": { - "@nodelib/fs.scandir": "2.1.3", + "@nodelib/fs.scandir": "2.1.4", "fastq": "^1.6.0" } }, @@ -41,9 +46,9 @@ "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==" }, "@types/node": { - "version": "14.11.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.11.2.tgz", - "integrity": "sha512-jiE3QIxJ8JLNcb1Ps6rDbysDhN4xa8DJJvuC9prr6w+1tIh+QAbYyNF3tyiZNLDBIuBCf4KEcV2UvQm/V60xfA==" + "version": "14.14.20", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.20.tgz", + "integrity": "sha512-Y93R97Ouif9JEOWPIUyU+eyIdyRqQR0I8Ez1dzku4hDx34NWh4HbtIc3WNzwB1Y9ULvNGeu5B8h8bVL5cAk4/A==" }, "aggregate-error": { "version": "3.1.0", @@ -154,9 +159,9 @@ } }, "fastq": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.8.0.tgz", - "integrity": "sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.10.0.tgz", + "integrity": "sha512-NL2Qc5L3iQEsyYzweq7qfgy5OtXCmGzGvhElGEd/SoFWEMOEczNh5s5ocaF01HDetxz+p8ecjNPA6cZxxIHmzA==", "requires": { "reusify": "^1.0.4" } @@ -370,9 +375,9 @@ } }, "run-parallel": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz", - "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==" + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.10.tgz", + "integrity": "sha512-zb/1OuZ6flOlH6tQyMPUrE3x3Ulxjlo9WIVXR4yVYi4H9UXQaeIsPbLn2R3O3vQCnDKkAl2qHiuocKKX4Tz/Sw==" }, "slash": { "version": "3.0.0", diff --git a/package.json b/package.json index 53b785670..c0276b2a6 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "sync-sass": "rsync -avr --delete node_modules/uswds/src/stylesheets/ _sass/uswds/src/" }, "dependencies": { + "@18f/private-eye": "^2.0.0", "uswds": "^2.9.0" }, "devDependencies": { diff --git a/uswds-jekyll.gemspec b/uswds-jekyll.gemspec index efb259776..dbd039f3d 100644 --- a/uswds-jekyll.gemspec +++ b/uswds-jekyll.gemspec @@ -2,7 +2,7 @@ Gem::Specification.new do |s| s.name = 'uswds-jekyll' - s.version = '5.2.0' + s.version = '5.3.0' s.authors = ['Shawn Allen', 'Tom Black', 'Brian Hurst', 'Scott Weber', 'Dan O. Williams'] s.email = ['daniel.williams@gsa.gov']