diff --git a/_js/behaviors/dismiss.js b/_js/behaviors/dismiss.js new file mode 100644 index 000000000..f0d283d6a --- /dev/null +++ b/_js/behaviors/dismiss.js @@ -0,0 +1,21 @@ +import closest from 'dom101/closest' +import remove from 'dom101/remove' +import on from 'dom101/on' +import { getData } from '../helpers/data' +import onmount from 'onmount' + +/** + * Dismiss button + */ + +onmount('[data-js-dismiss]', function () { + const parent = closest(this, '[data-js-dismissable]') + const dismissable = getData(parent, 'js-dismissable') + const id = dismissable && dismissable.id || '' + + on(this, 'click', e => { + Dismiss.setDismissed(id) + e.preventDefault() + if (parent) remove(parent) + }) +}) diff --git a/_js/behaviors/dismissable.js b/_js/behaviors/dismissable.js new file mode 100644 index 000000000..f2708f4ee --- /dev/null +++ b/_js/behaviors/dismissable.js @@ -0,0 +1,17 @@ +import onmount from 'onmount' +import remove from 'dom101/remove' +import removeClass from 'dom101/remove-class' + +import { getData } from '../helpers/data' +import { isDismissed } from '../helpers/dismiss' +import { isPreview } from '../helpers/preview' + +onmount('[data-js-dismissable]', function () { + const id = getData(this, 'js-dismissable').id || '' + + if (isPreview() || isDismissed(id)) { + remove(this) + } else { + removeClass(this, '-hide') + } +}) diff --git a/_js/behaviors/disqus.js b/_js/behaviors/disqus.js new file mode 100644 index 000000000..b0d8c0c65 --- /dev/null +++ b/_js/behaviors/disqus.js @@ -0,0 +1,17 @@ +import onmount from 'onmount' +import injectDisqus from '../helpers/inject_disqus' + +/** + * Injects Disqus onto the page. + */ + +onmount('[data-js-disqus]', function () { + const data = JSON.parse(this.getAttribute('data-js-disqus')) + + window.disqus_config = function () { + this.page.url = data.url + this.page.identifier = data.identifier + } + + injectDisqus(data.host) +}) diff --git a/_js/behaviors/no-preview.js b/_js/behaviors/no-preview.js index 4bf5fe67c..bbcc53b23 100644 --- a/_js/behaviors/no-preview.js +++ b/_js/behaviors/no-preview.js @@ -3,12 +3,14 @@ import ready from 'dom101/ready' import remove from 'dom101/remove' import onmount from 'onmount' +import { isPreview } from '../helpers/preview' + /* * Behavior: Things to remove when preview mode is on */ onmount('[data-js-no-preview]', function (b) { - if (~window.location.search.indexOf('preview=1')) { + if (isPreview()) { remove(this) } }) diff --git a/_js/behaviors/searchable-item.js b/_js/behaviors/searchable-item.js new file mode 100644 index 000000000..e6c1869dc --- /dev/null +++ b/_js/behaviors/searchable-item.js @@ -0,0 +1,12 @@ + +import $ from 'jquery' +import onmount from 'onmount' +import permutate from '../helpers/permutate' + +onmount('[data-js-searchable-item]', function () { + const $this = $(this) + const data = $this.data('js-searchable-item') + const words = permutate(data) + + $this.attr('data-search-index', words.join(' ')) +}) diff --git a/_js/helpers/data.js b/_js/helpers/data.js new file mode 100644 index 000000000..2e74e4285 --- /dev/null +++ b/_js/helpers/data.js @@ -0,0 +1,20 @@ +/** + * Stores and retrieves data from an element. Works like jQuery.data(). + */ + +export function data (el, key, val) { + if (typeof val !== 'undefined') { + return getData(el, key) + } else { + return setData(el, key, val) + } +} + +export function getData (el, key) { + const str = el.getAttribute('data-' + key) + return JSON.parse(str || '{}') +} + +export function setData (el, key, val) { + el.setAttribute('data-' + key, JSON.stringify(val)) +} diff --git a/_js/helpers/dismiss.js b/_js/helpers/dismiss.js new file mode 100644 index 000000000..b629d2b71 --- /dev/null +++ b/_js/helpers/dismiss.js @@ -0,0 +1,28 @@ +import * as Store from './store' + +/** + * Dismisses an announcement. + * + * @example + * setDismissed('2017-09-02-happy-birthday') + */ + +export function setDismissed (id) { + Store.update('dismissed', function (data) { + data[id] = true + return data + }) +} + +/** + * Checks if an announcement has been dismissed before. + * + * @example + * setDismissed('2017-09-02-happy-birthday') + * isDismissed('2017-09-02-happy-birthday') => true + */ + +export function isDismissed (id) { + const data = Store.fetch('dismissed') + return data && data[id] +} diff --git a/_js/helpers/inject_disqus.js b/_js/helpers/inject_disqus.js new file mode 100644 index 000000000..48fbeb5d5 --- /dev/null +++ b/_js/helpers/inject_disqus.js @@ -0,0 +1,14 @@ +/** + * Injects disqus's scripts into the page. + * + * @example + * injectDisqus('devhints.disqus.com') + */ + +export default function injectDisqus (host) { + var d = document, s = d.createElement('script') + s.src = 'https://' + host + '/embed.js' + s.setAttribute('data-timestamp', +new Date()) + ;(d.head || d.body).appendChild(s) +} + diff --git a/_js/helpers/permutate.js b/_js/helpers/permutate.js new file mode 100644 index 000000000..c440b9077 --- /dev/null +++ b/_js/helpers/permutate.js @@ -0,0 +1,71 @@ +/** + * Permutates a searcheable item. + * + * permutate({ + * slug: 'hello-world', + * category: 'greetings' + * }) + */ + +export default function permutate (data) { + let words = [] + if (data.slug) { + words = words.concat(permutateString(data.slug)) + } + if (data.category) { + words = words.concat(permutateString(data.category)) + } + return words +} + +/* + * Permutates strings. + * + * @example + * permutateString('hi joe') + * => ['h', 'hi', 'j', 'jo', 'joe'] + */ + +export function permutateString (str) { + let words = [] + let inputs = splitwords(str) + + inputs.forEach(word => { + words = words.concat(permutateWord(word)) + }) + + return words +} + +/** + * Permutates a word. + * + * @example + * permutateWord('hello') + * => ['h', 'he', 'hel', 'hell', 'hello'] + */ + +export function permutateWord (str) { + let words = [] + const len = str.length + for (var i = 1; i <= len; ++i) { + words.push(str.substr(0, i)) + } + return words +} + +/** + * Helper for splitting to words. + * + * @example + * splitWords('Hello, world!') + * => ['hello', 'world'] + */ + +export function splitwords (str) { + const words = str.toLowerCase() + .split(/[ \/\-_]/) + .filter(k => k && k.length !== 0) + + return words +} diff --git a/_js/helpers/preview.js b/_js/helpers/preview.js new file mode 100644 index 000000000..670c09c68 --- /dev/null +++ b/_js/helpers/preview.js @@ -0,0 +1,7 @@ +/** + * Checks if we're in preview mode (?preview=1). + */ + +export function isPreview () { + return window.location.search.indexOf('preview=1') !== -1 +} diff --git a/_js/helpers/store.js b/_js/helpers/store.js new file mode 100644 index 000000000..8da6af718 --- /dev/null +++ b/_js/helpers/store.js @@ -0,0 +1,29 @@ +/** + * Updates a local storage key. If it doesn't exist, it defaults to an empty + * object. + * + * @example + * update('dismissed', (data) => { + * data.lol = true + * return data + * }) + */ + +export function update (key, fn) { + if (!window.localStorage) return + let data = JSON.parse(window.localStorage[key] || '{}') + data = fn(data) + window.localStorage[key] = JSON.stringify(data) +} + +/** + * Fetches a local storage key. + * + * @example + * const data = fetch('dismissed') + */ + +export function fetch (key) { + if (!window.localStorage) return + return JSON.parse(window.localStorage[key] || '{}') +} diff --git a/assets/2017/script.js b/assets/2017/script.js index 8cefebabb..f0d92b036 100644 --- a/assets/2017/script.js +++ b/assets/2017/script.js @@ -3,14 +3,6 @@ */ $(function () { - $('[data-js-searchable-item]').each(function () { - const $this = $(this) - const data = $this.data('js-searchable-item') - const words = permutate(data) - - $this.attr('data-search-index', words.join(' ')) - }) - // Propagate item search indices to headers $('[data-js-searchable-header]').each(function () { const $this = $(this) @@ -65,100 +57,6 @@ $(function () { }) }) -/* - * Behavior: Disqus - */ - -$(function () { - $('[data-js-disqus]').each(function () { - const $this = $(this) - const data = $this.data('js-disqus') - - window.disqus_config = function () { - this.page.url = data.url - this.page.identifier = data.identifier - } - - injectDisqus(data.host) - }) -}) - -/* - * Behavior: dismiss button - */ - -$(function () { - $('[data-js-dismiss]').each(function () { - var $button = $(this) - var $parent = $button.closest('[data-js-dismissable]') - var id = $parent.data('js-dismissable').id || '' - - $button.on('click', function (e) { - Dismiss.setDismissed(id) - e.preventDefault() - $parent.remove() - }) - }) - - $('[data-js-dismissable]').each(function () { - var $this = $(this) - var id = $this.data('js-dismissable').id || '' - const isDismissed = Dismiss.isDismissed(id) - if (isDismissed || window.location.search.indexOf('preview') !== -1) { - $this.remove() - } else { - $this.removeClass('-hide') - } - }) -}) - -/* - * Helper: dismissed - */ - -const Dismiss = { - setDismissed: function (id) { - Store.update('dismissed', function (data) { - data[id] = true - return data - }) - }, - - isDismissed: function (id) { - const data = Store.fetch('dismissed') - return data && data[id] - } -} - -/* - * Simple LocalStorage shim - */ - -const Store = { - update: function (key, fn) { - if (!window.localStorage) return - let data = JSON.parse(window.localStorage[key] || '{}') - data = fn(data) - window.localStorage[key] = JSON.stringify(data) - }, - - fetch: function (key) { - if (!window.localStorage) return - return JSON.parse(window.localStorage[key] || '{}') - } -} - -/* - * Helper: injects disqus - */ - -function injectDisqus (host) { - var d = document, s = d.createElement('script') - s.src = 'https://' + host + '/embed.js' - s.setAttribute('data-timestamp', +new Date()) - ;(d.head || d.body).appendChild(s) -} - /* * Helper for splitting to words */ @@ -194,40 +92,6 @@ const Search = { } } -/* - * Permutator - */ - -function permutate (data) { - let words = [] - if (data.slug) { - words = words.concat(permutateString(data.slug)) - } - if (data.category) { - words = words.concat(permutateString(data.category)) - } - return words -} - -function permutateString (str) { - let words = [] - let inputs = splitwords(str) - - inputs.forEach(word => { - words = words.concat(permutateWord(word)) - }) - - return words -} - -function permutateWord (str) { - let words = [] - const len = str.length - for (var i = 1; i <= len; ++i) { - words.push(str.substr(0, i)) - } - return words -} /* * Helper: minimal qs implementation