diff --git a/_includes/meta.html b/_includes/meta.html index 6079b5762..f9e3e2c29 100644 --- a/_includes/meta.html +++ b/_includes/meta.html @@ -82,3 +82,4 @@ gtag('config','{{ site.data.google_analytics.id }}'); {% endif %} + diff --git a/_js/app.js b/_js/app.js index ce21b1b81..1099f465c 100644 --- a/_js/app.js +++ b/_js/app.js @@ -1,5 +1,4 @@ // 3rd party libs -window.jQuery = window.$ = require('jquery') window.Prism = require('prismjs') // All the others diff --git a/_js/behaviors/searchable-header.js b/_js/behaviors/searchable-header.js index b2880b61e..a9947944b 100644 --- a/_js/behaviors/searchable-header.js +++ b/_js/behaviors/searchable-header.js @@ -1,5 +1,6 @@ import onmount from 'onmount' -import $ from 'jquery' +import { nextUntil } from '../helpers/dom' +import matches from 'dom101/matches' // Ensure that search-index is set first import './searchable-item' @@ -9,16 +10,13 @@ import './searchable-item' */ onmount('[data-js-searchable-header]', function () { - const $this = $(this) - const $els = $this - .nextUntil('[data-js-searchable-header]') - .filter('[data-search-index]') + const els = nextUntil(this, '[data-js-searchable-header]') + .filter(el => matches(el, '[data-search-index]')) - const keywords = $els - .map(function () { return $(this).attr('data-search-index') }) - .get() + const keywords = els + .map(n => n.getAttribute('data-search-index')) .join(' ') .split(' ') - $this.attr('data-search-index', keywords.join(' ')) + this.setAttribute('data-search-index', keywords.join(' ')) }) diff --git a/_js/helpers/dom.js b/_js/helpers/dom.js new file mode 100644 index 000000000..bf94941cb --- /dev/null +++ b/_js/helpers/dom.js @@ -0,0 +1,61 @@ +import matches from 'dom101/matches' + +/* + * Just like jQuery.append + */ + +export function appendMany (el, children) { + children.forEach(child => { el.appendChild(child) }) +} + +/* + * Just like jQuery.nextUntil + */ + +export function nextUntil (el, selector) { + const nextEl = el.nextSibling + return nextUntilTick(nextEl, selector, []) +} + +function nextUntilTick (el, selector, acc) { + if (!el) return acc + + const isMatch = matches(el, selector) + if (isMatch) return acc + + return nextUntilTick(el.nextSibling, selector, [ ...acc, el ]) +} + +/* + * Just like jQuery.before + */ + +export function before (reference, newNode) { + reference.parentNode.insertBefore(newNode, reference) +} + +/* + * Like jQuery.children('selector') + */ + +export function findChildren (el, selector) { + return [].slice.call(el.children) + .filter(child => matches(child, selector)) +} + +/** + * Creates a div + * @private + * + * @example + * + * createDiv({ class: 'foo' }) + */ + +export function createDiv (props) { + const d = document.createElement('div') + Object.keys(props).forEach(key => { + d.setAttribute(key, props[key]) + }) + return d +} diff --git a/_js/helpers/noop.js b/_js/helpers/noop.js new file mode 100644 index 000000000..f52fd393b --- /dev/null +++ b/_js/helpers/noop.js @@ -0,0 +1 @@ +/* blank */ diff --git a/_js/initializers/onmount.js b/_js/initializers/onmount.js index 3a0e056b2..c15ab6e68 100644 --- a/_js/initializers/onmount.js +++ b/_js/initializers/onmount.js @@ -1,6 +1,7 @@ import wrapify from '../wrapify' import ready from 'dom101/ready' import onmount from 'onmount' +import addClass from 'dom101/add-class' /** * Behavior: Wrapping @@ -8,6 +9,9 @@ import onmount from 'onmount' ready(() => { const body = document.querySelector('[data-js-main-body]') - if (body) { wrapify(body) } + if (body) { + wrapify(body) + addClass(body, '-wrapified') + } setTimeout(() => { onmount() }) }) diff --git a/_js/wrapify/__tests__/__snapshots__/index.test.js.snap b/_js/wrapify/__tests__/__snapshots__/index.test.js.snap index 867f5e50f..6e26ba7e5 100644 --- a/_js/wrapify/__tests__/__snapshots__/index.test.js.snap +++ b/_js/wrapify/__tests__/__snapshots__/index.test.js.snap @@ -22,17 +22,17 @@ exports[`h3 with class 1`] = `
+ +

(install)

+ +
- - - - `; @@ -72,9 +72,14 @@ exports[`multiple h2s 1`] = `
+ +

(install)

+ + +
+ +

(usage)

+ + +
- - - - - - - - - -
@@ -135,9 +135,14 @@ exports[`multiple h2s 1`] = `
+ +

(first)

+ + +
+ +

(second)

+ +
- - - - - - - - - `; @@ -204,9 +204,14 @@ exports[`simple usage 1`] = `
+ +

(install)

+ + +
+ +

(usage)

+ +
- - - - - - - - - `; diff --git a/_js/wrapify/index.js b/_js/wrapify/index.js index 2e3abcd38..e2b93f589 100644 --- a/_js/wrapify/index.js +++ b/_js/wrapify/index.js @@ -1,63 +1,110 @@ -import $ from 'jquery' +import matches from 'dom101/matches' +import addClass from 'dom101/add-class' +import { appendMany, nextUntil, before, findChildren, createDiv } from '../helpers/dom' -/* +/** * Wraps h2 sections into h2-section. * Wraps h3 sections into h3-section. + * + * @private */ export default function wrapify (root) { - const $root = $(root) - const $h2sections = groupify($root, { - tag: 'h2', - wrapper: '
', - body: '
' + // These are your H2 sections. Returns a list of .h2-section nodes. + const sections = wrapifyH2(root) + + // For each h2 section, wrap the H3's in them + sections.forEach(section => { + const bodies = findChildren(section, '[data-js-h3-section-list]') + bodies.forEach(body => { wrapifyH3(body) }) }) +} - $h2sections.each(function () { - const $body = $(this).children('[data-js-h3-section-list]') +/** + * Wraps h2 sections into h2-section. + * Creates and HTML structure like so: + * + * .h2-section + * h2. + * (title) + * .body.h3-section-list. + * (body goes here) + * + * @private + */ - groupify($body, { - tag: 'h3', - wrapper: '
', - body: '
' +function wrapifyH2 (root) { + return groupify(root, { + tag: 'h2', + wrapperFn: () => createDiv({ class: 'h2-section' }), + bodyFn: () => createDiv({ + class: 'body h3-section-list', + 'data-js-h3-section-list': '' }) }) } -/* - * Groups stuff +/** + * Wraps h3 sections into h3-section. + * Creates and HTML structure like so: + * + * .h3-section + * h3. + * (title) + * .body. + * (body goes here) + * + * @private */ -export function groupify ($this, { tag, wrapper, body }) { - const $first = $this.children(':first-child') - let $result = $() +function wrapifyH3 (root) { + return groupify(root, { + tag: 'h3', + wrapperFn: () => createDiv({ class: 'h3-section' }), + bodyFn: () => createDiv({ class: 'body' }) + }) +} + +/** + * Groups all headings (a `tag` selector) under wrappers like `.h2-section` + * (build by `wrapperFn()`). + * @private + */ + +export function groupify (el, { tag, wrapperFn, bodyFn }) { + const first = el.children[0] + let result = [] // Handle the markup before the first h2 - if (!$first.is(tag)) { - const $sibs = $first.nextUntil(tag) - $result = $result.add(wrap($first, null, $first.add($sibs))) + if (first && !matches(first, tag)) { + const sibs = nextUntil(first, tag) + result.push(wrap(first, null, [ first, ...sibs ])) } - $this.children(tag).each(function () { - const $sibs = $(this).nextUntil(tag) - const $heading = $(this) - $result = $result.add(wrap($heading, $heading, $sibs)) + // Find all h3's inside it + const children = findChildren(el, tag) + + children.forEach(child => { + const sibs = nextUntil(child, tag) + result.push(wrap(child, child, sibs)) }) - return $result + return result - function wrap ($pivot, $first, $sibs) { - const $wrap = $(wrapper) - $wrap.addClass($pivot.attr('class')) - $pivot.before($wrap) + function wrap (pivot, first, sibs) { + const wrap = wrapperFn() - const $body = $(body) - $body.addClass($pivot.attr('class')) - $body.append($sibs) + const pivotClass = pivot.className + if (pivotClass) addClass(wrap, pivotClass) + before(pivot, wrap) - if ($first) $wrap.append($first) - $wrap.append($body) + const body = bodyFn() + if (pivotClass) addClass(body, pivotClass) + appendMany(body, sibs) - return $wrap + if (first) wrap.appendChild(first) + wrap.appendChild(body) + + return wrap } } diff --git a/_layouts/2017/sheet.html b/_layouts/2017/sheet.html index 1d7585c60..86003b313 100644 --- a/_layouts/2017/sheet.html +++ b/_layouts/2017/sheet.html @@ -1,5 +1,5 @@ - + {% include 2017/head.html %} {% include 2017/article-schema.html page=page %} diff --git a/_sass/2017/base/base.scss b/_sass/2017/base/base.scss index 60f80b5d4..9cf3a9237 100644 --- a/_sass/2017/base/base.scss +++ b/_sass/2017/base/base.scss @@ -69,3 +69,18 @@ a:hover { } } } + +/* + * Hide markdown before it's wrapped. + */ + +html.WithJs .MarkdownBody { + & { + opacity: 0; + } + + &.-wrapified { + opacity: 1; + transition: opacity 250ms linear; + } +} diff --git a/_sass/2017/components/code-sponsor.scss b/_sass/2017/components/code-sponsor.scss index 921d574c8..de1a84d1a 100644 --- a/_sass/2017/components/code-sponsor.scss +++ b/_sass/2017/components/code-sponsor.scss @@ -3,6 +3,7 @@ text-align: center; position: relative; height: 130px; + overflow: hidden; } /* Prelude */ diff --git a/_support/webpack.config.js b/_support/webpack.config.js index 362017537..051ebff89 100644 --- a/_support/webpack.config.js +++ b/_support/webpack.config.js @@ -8,7 +8,6 @@ module.exports = { app: './_js/app.js', vendor: [ // Large 3rd-party libs - 'jquery', 'prismjs', // Prism plugins @@ -48,6 +47,12 @@ module.exports = { } ] }, + resolve: { + alias: { + // Never bundle jQuery + 'jquery': join(__dirname, '..', '_js/helpers/noop.js') + } + }, stats: 'minimal', plugins: [ // Optimize module ID's for vendor chunks diff --git a/assets/packed/app.js b/assets/packed/app.js index 0263503b3..294cd786f 100644 --- a/assets/packed/app.js +++ b/assets/packed/app.js @@ -1,4 +1,4 @@ -webpackJsonp([0],{"/k7tj9kxRFhFtZjlt346":function(t,e,i){"use strict";function n(t,e){if(window.localStorage){var i=JSON.parse(window.localStorage[t]||"{}");i=e(i),window.localStorage[t]=JSON.stringify(i)}}function o(t){if(window.localStorage)return JSON.parse(window.localStorage[t]||"{}")}Object.defineProperty(e,"__esModule",{value:!0}),e.update=n,e.fetch=o},"/n6t+V4ehwYkVVtDRmGr":function(t,e,i){"use strict";function n(t){o((0,s.default)(t),{tag:"h2",wrapper:'
',body:'
'}).each(function(){o((0,s.default)(this).children("[data-js-h3-section-list]"),{tag:"h3",wrapper:'
',body:'
'})})}function o(t,e){function i(t,e,i){var n=(0,s.default)(o);n.addClass(t.attr("class")),t.before(n);var a=(0,s.default)(r);return a.addClass(t.attr("class")),a.append(i),e&&n.append(e),n.append(a),n}var n=e.tag,o=e.wrapper,r=e.body,a=t.children(":first-child"),u=(0,s.default)();if(!a.is(n)){var h=a.nextUntil(n);u=u.add(i(a,null,a.add(h)))}return t.children(n).each(function(){var t=(0,s.default)(this).nextUntil(n),e=(0,s.default)(this);u=u.add(i(e,e,t))}),u}Object.defineProperty(e,"__esModule",{value:!0}),e.default=n,e.groupify=o;var r=i("gCzbTwtZHf0SKa8ni0Jr"),s=function(t){return t&&t.__esModule?t:{default:t}}(r)},"2QOxTCxkuzN0PP2kJ2jn":function(t,e,i){"use strict";function n(t){s.update("dismissed",function(e){return e[t]=!0,e})}function o(t){var e=s.fetch("dismissed");return e&&e[t]}Object.defineProperty(e,"__esModule",{value:!0}),e.setDismissed=n,e.isDismissed=o;var r=i("/k7tj9kxRFhFtZjlt346"),s=function(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var i in t)Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e.default=t,e}(r)},"2tov+8o2NrZdw/Lg8JZA":function(t,e,i){function n(t){return i(o(t))}function o(t){var e=r[t];if(!(e+1))throw new Error("Cannot find module '"+t+"'.");return e}var r={"./onmount.js":"6hLKOGpXaWUwTDQA5TL2"};n.keys=function(){return Object.keys(r)},n.resolve=o,t.exports=n,n.id="2tov+8o2NrZdw/Lg8JZA"},"6hLKOGpXaWUwTDQA5TL2":function(t,e,i){"use strict";function n(t){return t&&t.__esModule?t:{default:t}}var o=i("/n6t+V4ehwYkVVtDRmGr"),r=n(o),s=i("tqbVxj9+xGAhlFrQpyTS"),a=n(s),u=i("yfX/NEeqeNrvWENPWWKS"),h=n(u);(0,a.default)(function(){var t=document.querySelector("[data-js-main-body]");t&&(0,r.default)(t),setTimeout(function(){(0,h.default)()})})},"BF4TFfJ+K+wsuHfLZ/S3":function(t,e,i){"use strict";function n(){return-1!==window.location.search.indexOf("preview=1")}Object.defineProperty(e,"__esModule",{value:!0}),e.isPreview=n},BuWMdGeXUEK0GmkN01pU:function(t,e,i){"use strict";function n(t){return t&&t.__esModule?t:{default:t}}var o=i("yfX/NEeqeNrvWENPWWKS"),r=n(o),s=i("cYqQDyTPtD7lV79ovj4Y"),a=n(s),u=i("tqbVxj9+xGAhlFrQpyTS"),h=n(u);(0,r.default)("[data-js-disqus]",function(){var t=JSON.parse(this.getAttribute("data-js-disqus"));window.disqus_config=function(){this.page.url=t.url,this.page.identifier=t.identifier},(0,h.default)(function(){(0,a.default)(t.host)})})},C2mk7N9JzKWlcOYJrkfi:function(t,e){function i(t,e){var i=t.matches||t.matchesSelector||t.msMatchesSelector||t.mozMatchesSelector||t.webkitMatchesSelector||t.oMatchesSelector;if(i)return i.call(t,e);if(t.parentNode){for(var n=t.parentNode.querySelectorAll(e),o=n.length;o--;0)if(n[o]===t)return!0;return!1}}t.exports=i},"CjZN/azBHJxH2NsOjfdD":function(t,e,i){"use strict";function n(t,e,i){return void 0!==i?o(t,e):r(t,e,i)}function o(t,e){var i=t.getAttribute("data-"+e);return JSON.parse(i||"{}")}function r(t,e,i){t.setAttribute("data-"+e,JSON.stringify(i))}Object.defineProperty(e,"__esModule",{value:!0}),e.data=n,e.getData=o,e.setData=r},"D3SYMdNAenu80VOO7/Dv":function(t,e,i){"use strict";function n(t){return t&&t.__esModule?t:{default:t}}var o=i("yfX/NEeqeNrvWENPWWKS"),r=n(o),s=i("MKJaVdnD9jF7rk6rW6AJ"),a=n(s);(0,r.default)("[data-js-search-form]",function(){(0,a.default)(this,"submit",function(t){t.preventDefault();var e=document.querySelector("a[data-search-index]:visible"),i=e&&e.getAttribute("href");i&&(window.location=i)})})},DfTyEU8lscjHXmCMlptO:function(t,e,i){"use strict";function n(){(0,a.default)("[data-search-index]").forEach(function(t){t.removeAttribute("aria-hidden")})}function o(t){var e=(0,r.splitwords)(t);if(!e.length)return n();var i=e.map(function(t){return"[data-search-index~="+JSON.stringify(t)+"]"}).join("");(0,a.default)("[data-search-index]").forEach(function(t){t.setAttribute("aria-hidden",!0)}),(0,a.default)(i).forEach(function(t){t.removeAttribute("aria-hidden")})}Object.defineProperty(e,"__esModule",{value:!0}),e.showAll=n,e.show=o;var r=i("GOdGag34TlRuHa2OZoh9"),s=i("EvxNxLK9yKFmn1U14Kvw"),a=function(t){return t&&t.__esModule?t:{default:t}}(s)},"E+dCGfS1pT5WowYv7jpv":function(t,e,i){"use strict";function n(t){return t&&t.__esModule?t:{default:t}}var o=i("GbzL7C/QFmiBZDICkla+"),r=n(o),s=i("yfX/NEeqeNrvWENPWWKS"),a=n(s),u=i("MKJaVdnD9jF7rk6rW6AJ"),h=n(u),c=i("EvxNxLK9yKFmn1U14Kvw"),d=n(c);(0,a.default)("[data-js-h3-section-list]",function(){var t=new r.default(this,{itemSelector:".h3-section",transitionDuration:0});(0,d.default)("img",this).forEach(function(e){(0,h.default)(e,"load",function(){t.layout()})})})},EvxNxLK9yKFmn1U14Kvw:function(t,e){function i(t,e){return Array.prototype.slice.call((e||document).querySelectorAll(t))}t.exports=i},GOdGag34TlRuHa2OZoh9:function(t,e,i){"use strict";function n(t){var e=[];return t.slug&&(e=e.concat(o(t.slug))),t.category&&(e=e.concat(o(t.category))),e}function o(t){var e=[];return s(t).forEach(function(t){e=e.concat(r(t))}),e}function r(t){for(var e=[],i=t.length,n=1;n<=i;++n)e.push(t.substr(0,n));return e}function s(t){return t.toLowerCase().split(/[ \/\-_]/).filter(function(t){return t&&0!==t.length})}Object.defineProperty(e,"__esModule",{value:!0}),e.default=n,e.permutateString=o,e.permutateWord=r,e.splitwords=s},"GbzL7C/QFmiBZDICkla+":function(t,e,i){var n,o,r,s,a,n,u,h,c,d,n,f,n,l,p,n,m,n,l,v,n,l,g,n,l,y,n,l,_,n,l,b,n,l,w,n,o;/*! +webpackJsonp([0],{"/k7tj9kxRFhFtZjlt346":function(t,e,i){"use strict";function n(t,e){if(window.localStorage){var i=JSON.parse(window.localStorage[t]||"{}");i=e(i),window.localStorage[t]=JSON.stringify(i)}}function o(t){if(window.localStorage)return JSON.parse(window.localStorage[t]||"{}")}Object.defineProperty(e,"__esModule",{value:!0}),e.update=n,e.fetch=o},"/n6t+V4ehwYkVVtDRmGr":function(t,e,i){"use strict";function n(t){return t&&t.__esModule?t:{default:t}}function o(t){if(Array.isArray(t)){for(var e=0,i=Array(t.length);e