Stun.utils = Stun.$u = { /** * Debounce * @param {Object} func Callback function * @param {Number} wait Waiting time * @param {Boolean} immediate Run immediately */ debounce: function (func, wait, immediate) { var timeout return function () { var context = this var args = arguments if (timeout) clearTimeout(timeout) if (immediate) { var callNow = !timeout timeout = setTimeout(function () { timeout = null }, wait) if (callNow) func.apply(context, args) } else { timeout = setTimeout(function () { func.apply(context, args) }, wait) } } }, /** * Throttle * @param {Object} func Callback function * @param {Number} wait Waiting time * @param {Object} options leading: Boolean, trailing: Boolean */ throttle: function (func, wait, options) { var timeout, context, args var previous = 0 if (!options) options = {} var later = function () { previous = options.leading === false ? 0 : new Date().getTime() timeout = null func.apply(context, args) if (!timeout) context = args = null } var throttled = function () { var now = new Date().getTime() if (!previous && options.leading === false) previous = now var remaining = wait - (now - previous) context = this args = arguments if (remaining <= 0 || remaining > wait) { if (timeout) { clearTimeout(timeout) timeout = null } previous = now func.apply(context, args) if (!timeout) context = args = null } else if (!timeout && options.trailing !== false) { timeout = setTimeout(later, remaining) } } return throttled }, hasMobileUA: function () { var nav = window.navigator var ua = nav.userAgent var pa = /iPad|iPhone|Android|Opera Mini|BlackBerry|webOS|UCWEB|Blazer|PSP|IEMobile|Symbian/g return pa.test(ua) }, isTablet: function () { return ( window.screen.width > 767 && window.screen.width < 992 && this.hasMobileUA() ) }, isMobile: function () { return window.screen.width < 767 && this.hasMobileUA() }, isDesktop: function () { return !this.isTablet() && !this.isMobile() }, Cookies: function () { function extend () { var i = 0 var result = {} for (; i < arguments.length; i++) { var attributes = arguments[i] for (var key in attributes) { result[key] = attributes[key] } } return result } function init (converter) { function api (key, value, attributes) { var result if (typeof document === 'undefined') { return } // Write if (arguments.length > 1) { attributes = extend({ path: '/' }, api.defaults, attributes) if (typeof attributes.expires === 'number') { var expires = new Date() expires.setMilliseconds( expires.getMilliseconds() + attributes.expires * 864e5 ) attributes.expires = expires } // We're using "expires" because "max-age" is not supported by IE attributes.expires = attributes.expires ? attributes.expires.toUTCString() : '' try { result = JSON.stringify(value) if (/^[{[]/.test(result)) { value = result } } catch (e) { /* empty */ } if (!converter.write) { value = encodeURIComponent(String(value)).replace( /%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g, decodeURIComponent ) } else { value = converter.write(value, key) } key = encodeURIComponent(String(key)) key = key.replace(/%(23|24|26|2B|5E|60|7C)/g, decodeURIComponent) key = key.replace(/[()]/g, escape) var stringifiedAttributes = '' for (var attributeName in attributes) { if (!attributes[attributeName]) { continue } stringifiedAttributes += '; ' + attributeName if (attributes[attributeName] === true) { continue } stringifiedAttributes += '=' + attributes[attributeName] } return (document.cookie = key + '=' + value + stringifiedAttributes) } // Read if (!key) { result = {} } // To prevent the for loop in the first place assign an empty array // in case there are no cookies at all. Also prevents odd result when // calling "get()" var cookies = document.cookie ? document.cookie.split('; ') : [] var rdecode = /(%[0-9A-Z]{2})+/g var i = 0 for (; i < cookies.length; i++) { var parts = cookies[i].split('=') var cookie = parts.slice(1).join('=') if (cookie.charAt(0) === '"') { cookie = cookie.slice(1, -1) } try { var name = parts[0].replace(rdecode, decodeURIComponent) cookie = converter.read ? converter.read(cookie, name) : converter(cookie, name) || cookie.replace(rdecode, decodeURIComponent) if (this.json) { try { cookie = JSON.parse(cookie) } catch (e) { /* empty */ } } if (key === name) { result = cookie break } if (!key) { result[name] = cookie } } catch (e) { /* empty */ } } return result } api.set = api api.get = function (key) { return api.call(api, key) } api.getJSON = function () { return api.apply({ json: true }, [].slice.call(arguments)) } api.defaults = {} api.remove = function (key, attributes) { api(key, '', extend(attributes, { expires: -1 })) } api.withConverter = init return api } return init(function () {}) }, showThemeInConsole: function () { var stunInfo = '主题不错?⭐star 支持一下 ->' var stunURL = 'https://github.com/liuyib/hexo-theme-stun' var stunNameStr = '\n\n ___ ___ ___ ___ \n /\\ \\ /\\ \\ /\\__\\ /\\__\\ \n /::\\ \\ \\:\\ \\ /:/ / /::| | \n /:/\\ \\ \\ \\:\\ \\ /:/ / /:|:| | \n _\\:\\ \\ \\ \\ /::\\ \\ /:/ / ___ /:/|:| |__ \n /\\ \\:\\ \\ \\__\\ /:/\\:\\__\\ /:/__/ /\\__\\ /:/ |:| /\\__\\ \n \\:\\ \\:\\ \\/__/ /:/ \\/__/ \\:\\ \\ /:/ / \\/__|:|/:/ / \n \\:\\ \\:\\__\\ /:/ / \\:\\ /:/ / |:/:/ / \n \\:\\/:/ / \\/__/ \\:\\/:/ / |::/ / \n \\::/ / \\::/ / /:/ / \n \\/__/ \\/__/ \\/__/ \n \n' var stunInfoStyle = 'background-color: #49b1f5; color: #fff; padding: 8px; font-size: 14px;' var stunURLStyle = 'background-color: #ffbca2; padding: 8px; font-size: 14px;' var stunNameStyle = 'background-color: #eaf8ff;' console.log( '%c%s%c%s%c%s', stunInfoStyle, stunInfo, stunURLStyle, stunURL, stunNameStyle, stunNameStr ) }, /** * Change the event code to keyCode. * @param {String} code Event code */ codeToKeyCode: function (code) { var codes = { ArrowLeft: 37, ArrowRight: 39, Escape: 27, Enter: 13 } return codes[code] }, /** * "Alert" component * @param {String} status The Status of message. Values: success / info / warning / error. * @param {String} text The text to show. * @param {Number} delay Message stay time (unit is 's', default 5s). */ popAlert: function (status, text, delay) { if ($('.stun-message').length !== 0) { $('.stun-message').remove() } var $alert = $( '
' ) $('body').append($alert) $(document).ready(function () { $('.stun-alert') .velocity('stop') .velocity('transition.slideDownBigIn', { duration: 300 }) .velocity('reverse', { delay: delay * 1000 || 5000, duration: 260, complete: function () { $('.stun-alert').css('display', 'none') } }) }) }, /** * Copy any text. * @param {HTMLElement} container Container of text. */ copyText: function (container) { try { var selection = window.getSelection() var range = document.createRange() // Select text by the content of node. range.selectNodeContents(container) selection.removeAllRanges() selection.addRange(range) var text = selection.toString() var input = document.createElement('input') // Create a temporary input to make the // execCommand command take effect. input.style.display = 'none' input.setAttribute('readonly', 'readonly') input.setAttribute('value', text) document.body.appendChild(input) input.setSelectionRange(0, -1) if (document.execCommand('copy')) { document.execCommand('copy') document.body.removeChild(input) return true } document.body.removeChild(input) } catch (e) { return false } }, // Wrap images with fancybox support. wrapImageWithFancyBox: function () { $('.content img') .not(':hidden') .each(function () { var $img = $(this) var imgTitle = $img.attr('title') || $img.attr('alt') var $imgWrap = $img.parent('a') var imgSource = ['data-src', 'data-original', 'src'] var imgSrc = '' if (!$imgWrap[0]) { for (var i = 0; i < imgSource.length; i++) { if ($img.attr(imgSource[i])) { imgSrc = $img.attr(imgSource[i]) break } } $imgWrap = $img .wrap(``) .parent('a') if ($img.is('.gallery img')) { $imgWrap.attr('data-fancybox', 'gallery') } else { $imgWrap.attr('data-fancybox', 'default') } } if (imgTitle) { $imgWrap.attr('title', imgTitle).attr('data-caption', imgTitle) } }) $().fancybox({ selector: '[data-fancybox]', loop: true, transitionEffect: 'slide', hash: false, buttons: [ 'share', 'slideShow', 'fullScreen', 'download', 'thumbs', 'close' ] }) }, // Display the image in the gallery as a waterfall. showImageToWaterfall: function () { var gConfig = CONFIG.galleryWaterfall var colWidth = parseInt(gConfig.colWidth) var colGapX = parseInt(gConfig.gapX) var GALLERY_IMG_SELECTOR = '.gallery img' this.waitAllImageLoad(GALLERY_IMG_SELECTOR, function () { $('.gallery').masonry({ itemSelector: GALLERY_IMG_SELECTOR, columnWidth: colWidth, percentPosition: true, gutter: colGapX, transitionDuration: 0 }) }) }, // Lazy load the images of post. lazyLoadImage: function () { $('img.lazyload').lazyload() }, // Add a mark icon to the link with `target="_blank"` attribute. addIconToExternalLink: function (container) { if (!$(container)[0]) { return } var $wrapper = $('') var $icon = $( '' + `` + '' ) $(container) .find('a[target="_blank"]') .addClass('exturl__link') .wrap($wrapper) .parent('.exturl') .append($icon) }, // Switch to the prev / next post by shortcuts. registerSwitchPost: function () { var keyLeft = this.codeToKeyCode('ArrowLeft') var keyRight = this.codeToKeyCode('ArrowRight') $(document).on('keydown', function (e) { var isPrev = e.keyCode === keyLeft var isNext = e.keyCode === keyRight if (e.ctrlKey) { if (isPrev) { var prevElem = $('.paginator-prev a')[0] prevElem && prevElem.click() } else if (isNext) { var nextElem = $('.paginator-next a')[0] nextElem && nextElem.click() } } }) }, // Show / Hide the reward QR. registerShowReward: function () { $('.reward-button').on('click', function () { var $container = $('.reward-qrcode') if ($container.is(':visible')) { $container.css('display', 'none') } else { $container.velocity('stop').velocity('transition.slideDownIn', { duration: 300 }) } }) }, // Click to zoom in image, without fancybox. registerZoomImage: function () { $('#content-wrap img') .not(':hidden') .each(function () { if ($(this).attr('data-zoom') === 'none') return $(this).addClass('zoomimg') }) var $imgMask = $('') var $imgClone = null var isZoom = false $(window).on('scroll', closeZoom) $(document).on('click', closeZoom) $('.zoomimg').on('click', function (e) { e.stopPropagation() if (isZoom) { closeZoom() return } isZoom = true $imgClone = $(this) .clone() .addClass('zoomimg-clone') var SIDE_GAP = parseInt(CONFIG.zoomImage.gapAside || 20) var imgRect = this.getBoundingClientRect() var imgOuterW = $(this).outerWidth() var imgOuterH = $(this).outerHeight() var imgW = $(this).width() var imgH = $(this).height() var imgL = $(this).offset().left + (imgOuterW - imgW) / 2 var imgT = $(this).offset().top + (imgOuterH - imgH) / 2 var winW = $(window).width() - SIDE_GAP * 2 var winH = $(window).height() - SIDE_GAP * 2 var scaleX = winW / imgW var scaleY = winH / imgH var scale = (scaleX < scaleY ? scaleX : scaleY) || 1 var translateX = winW / 2 - (imgRect.x + imgOuterW / 2) + SIDE_GAP var translateY = winH / 2 - (imgRect.y + imgOuterH / 2) + SIDE_GAP $(this).addClass('zoomimg--hide') $('body') .append($imgMask) .append($imgClone) $imgMask.velocity({ opacity: 1 }) $imgClone.css({ left: imgL, top: imgT, width: imgW, height: imgH }) $imgClone.velocity( { translateX: translateX, translateY: translateY, scale: scale }, { duration: 300 } ) }) function closeZoom () { if (!isZoom) { return } isZoom = false $imgClone.velocity('reverse') $imgMask.velocity('reverse', { complete: function () { $imgMask.remove() $imgClone.remove() $('.zoomimg').removeClass('zoomimg--hide') } }) } }, /** * Add the header to code block. * @param {string} type The type of header. value: 'carbon' | null. */ addCodeHeader: function (type) { $('figure.highlight').each(function () { if (!$(this).find('figcaption')[0]) { var content = '' if (!type) { var CODEBLOCK_CLASS_NAME = 'highlight' var lang = $(this) .attr('class') .split(/\s/) .filter(function (e) { return e !== CODEBLOCK_CLASS_NAME }) content += `