core/router/utils/links.js

import * as R from 'ramda'
import { segmentize, beautifyPath } from '@/core/utils/strings'
import { parseUrl } from './parseUrl'

import local from 'local-links'

/**
 *
 * @memberof RouterUtils
 * @function localLinks
 * @description adds target blank and rel noopener attributes to external links
 * @property {HTMLElement} scope - the element to query from
 * @return {Object}
 *
 */
export const localLinks = scope =>
	[...scope.querySelectorAll('a')]
		.filter(
			a =>
				local.isLocal('click', a, true) === null &&
				a.href !== window.location.href
		)
		.forEach(a => {
			log(a.href)
			a.setAttribute('target', '_blank')
			a.setAttribute('rel', 'noopener')
		})

/**
 *
 * @memberof RouterUtils
 * @function preventClick
 * @description checks whether a link should be handled by the router
 * @author https://github.com/luruke/barba.js/blob/master/src/Pjax/Pjax.js
 * @property {Object} evt - the event object
 * @property {HTMLElement} element - the anchor being tested
 * @return {Boolean}
 *
 */
export function preventClick(evt, element) {
	if (!element || !element.href) return

	const { href } = element

	// Middle click, cmd click, and ctrl click
	if (evt.which > 1 || evt.metaKey || evt.ctrlKey || evt.shiftKey || evt.altKey)
		return

	// Ignore target with _blank target
	if (element.target && element.target === '_blank') return

	// Check if it's the same domain
	if (
		window.location.protocol !== element.protocol ||
		window.location.hostname !== element.hostname
	)
		return

	// Check if the port is the same

	// Ignore case when a hash is being tacked on the current URL
	if (href.indexOf('#') > -1) return

	// Ignore case where there is download attribute
	if (
		element.getAttribute &&
		typeof element.getAttribute('download') === 'string'
	)
		return

	if (element.hasAttribute('data-no-route')) return

	return true
}
/**
 *
 * @memberof RouterUtils
 * @function activeLinks
 * @description add active classes to any active elements
 * @return {function}
 *
 */
export const activeLinks = (() => {
	let previous
	let $anchors = []

	const defaultClasses = {
		match: 'is-current',
		root: 'is-current-root',
		parent: 'is-current-parent'
	}

	return ({ scope, classes }) => {
		$anchors = scope

		const classNames = {
			...defaultClasses,
			...classes
		}

		return url => {
			const { path, segments: testSegments } = parseUrl(url)

			if (previous) {
				R.forEach(({ node, className }) => {
					if (classNames[className]) {
						node.classList.remove(classNames[className])
					}
				})(previous)

				previous = []
			}

			previous = R.compose(
				R.forEach(({ node, className }) => {
					if (classNames[className]) {
						node.classList.add(classNames[className])
					}
				}),
				R.reduce((acc, [, value]) => {
					acc.push(...value)
					return acc
				}, []),
				Object.entries,
				R.map(
					R.compose(
						R.filter(
							({ segments, matchAgainst }) =>
								matchAgainst === R.join('/', segments)
						),
						R.map(node => {
							const { href } = node
							const { path: pathname } = parseUrl(href)

							const segments = segmentize(pathname)
							const length = R.length(segments)
							const matchAgainst = R.compose(R.join('/'), R.take(length))(
								testSegments
							)

							return {
								node,
								segments,
								matchAgainst,
								className:
									beautifyPath(pathname) === path
										? 'match'
										: length === 1 ? 'root' : 'parent'
							}
						})
					)
				),
				R.groupBy(({ pathname }) => segmentize(pathname).length),
				R.filter(item => {
					const { href } = item
					const { path: pathname } = parseUrl(href)

					const segments = segmentize(pathname)
					return (
						R.head(segments) === R.head(testSegments) &&
						segments.length <= testSegments.length
					)
				})
			)($anchors)
		}
	}
})()