diff options
Diffstat (limited to 'app/assets/javascripts')
4 files changed, 137 insertions, 0 deletions
diff --git a/app/assets/javascripts/feature_highlight/feature_highlight.js b/app/assets/javascripts/feature_highlight/feature_highlight.js new file mode 100644 index 00000000000..d65cc6d5d7d --- /dev/null +++ b/app/assets/javascripts/feature_highlight/feature_highlight.js @@ -0,0 +1,65 @@ +import _ from 'underscore'; +import { + getSelector, + togglePopover, + inserted, + mouseenter, + mouseleave, +} from './feature_highlight_helper'; + +export function setupFeatureHighlightPopover(id, debounceTimeout = 300) { + const $selector = $(getSelector(id)); + const $parent = $selector.parent(); + const $popoverContent = $parent.siblings('.feature-highlight-popover-content'); + const hideOnScroll = togglePopover.bind($selector, false); + const debouncedMouseleave = _.debounce(mouseleave, debounceTimeout); + + $selector + // Setup popover + .data('content', $popoverContent.prop('outerHTML')) + .popover({ + html: true, + // Override the existing template to add custom CSS classes + template: ` + <div class="popover feature-highlight-popover" role="tooltip"> + <div class="arrow"></div> + <div class="popover-content"></div> + </div> + `, + }) + .on('mouseenter', mouseenter) + .on('mouseleave', debouncedMouseleave) + .on('inserted.bs.popover', inserted) + .on('show.bs.popover', () => { + window.addEventListener('scroll', hideOnScroll); + }) + .on('hide.bs.popover', () => { + window.removeEventListener('scroll', hideOnScroll); + }) + // Display feature highlight + .removeAttr('disabled'); +} + +export function findHighestPriorityFeature() { + let priorityFeature; + + const sortedFeatureEls = [].slice.call(document.querySelectorAll('.js-feature-highlight')).sort((a, b) => + (a.dataset.highlightPriority || 0) < (b.dataset.highlightPriority || 0)); + + const [priorityFeatureEl] = sortedFeatureEls; + if (priorityFeatureEl) { + priorityFeature = priorityFeatureEl.dataset.highlight; + } + + return priorityFeature; +} + +export function highlightFeatures() { + const priorityFeature = findHighestPriorityFeature(); + + if (priorityFeature) { + setupFeatureHighlightPopover(priorityFeature); + } + + return priorityFeature; +} diff --git a/app/assets/javascripts/feature_highlight/feature_highlight_helper.js b/app/assets/javascripts/feature_highlight/feature_highlight_helper.js new file mode 100644 index 00000000000..939d12237f3 --- /dev/null +++ b/app/assets/javascripts/feature_highlight/feature_highlight_helper.js @@ -0,0 +1,59 @@ +import axios from '../lib/utils/axios_utils'; +import { __ } from '../locale'; +import Flash from '../flash'; +import LazyLoader from '../lazy_loader'; + +export const getSelector = highlightId => `.js-feature-highlight[data-highlight=${highlightId}]`; + +export function togglePopover(show) { + const isAlreadyShown = this.hasClass('js-popover-show'); + if ((show && isAlreadyShown) || (!show && !isAlreadyShown)) { + return false; + } + this.popover(show ? 'show' : 'hide'); + this.toggleClass('disable-animation js-popover-show', show); + + return true; +} + +export function dismiss(highlightId) { + axios.post(this.attr('data-dismiss-endpoint'), { + feature_name: highlightId, + }) + .catch(() => Flash(__('An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again.'))); + + togglePopover.call(this, false); + this.hide(); +} + +export function mouseleave() { + if (!$('.popover:hover').length > 0) { + const $featureHighlight = $(this); + togglePopover.call($featureHighlight, false); + } +} + +export function mouseenter() { + const $featureHighlight = $(this); + + const showedPopover = togglePopover.call($featureHighlight, true); + if (showedPopover) { + $('.popover') + .on('mouseleave', mouseleave.bind($featureHighlight)); + } +} + +export function inserted() { + const popoverId = this.getAttribute('aria-describedby'); + const highlightId = this.dataset.highlight; + const $popover = $(this); + const dismissWrapper = dismiss.bind($popover, highlightId); + + $(`#${popoverId} .dismiss-feature-highlight`) + .on('click', dismissWrapper); + + const lazyImg = $(`#${popoverId} .feature-highlight-illustration`)[0]; + if (lazyImg) { + LazyLoader.loadImage(lazyImg); + } +} diff --git a/app/assets/javascripts/feature_highlight/feature_highlight_options.js b/app/assets/javascripts/feature_highlight/feature_highlight_options.js new file mode 100644 index 00000000000..212643b1e04 --- /dev/null +++ b/app/assets/javascripts/feature_highlight/feature_highlight_options.js @@ -0,0 +1,12 @@ +import { highlightFeatures } from './feature_highlight'; +import bp from '../breakpoints'; + +export default function domContentLoaded() { + if (bp.getBreakpointSize() === 'lg') { + highlightFeatures(); + return true; + } + return false; +} + +document.addEventListener('DOMContentLoaded', domContentLoaded); diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index 39445a85c77..b99cb257ce3 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -26,6 +26,7 @@ import './gl_dropdown'; import initTodoToggle from './header'; import initImporterStatus from './importer_status'; import initLayoutNav from './layout_nav'; +import './feature_highlight/feature_highlight_options'; import LazyLoader from './lazy_loader'; import initLogoAnimation from './logo'; import './milestone_select'; |