mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-03 21:43:21 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			261 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			261 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
const render_hotspot_overlay = require('../templates/hotspot_overlay.hbs');
 | 
						|
const render_intro_reply_hotspot = require('../templates/intro_reply_hotspot.hbs');
 | 
						|
 | 
						|
// popover orientations
 | 
						|
const TOP = 'top';
 | 
						|
const LEFT = 'left';
 | 
						|
const RIGHT = 'right';
 | 
						|
const BOTTOM = 'bottom';
 | 
						|
const LEFT_BOTTOM = 'left_bottom';
 | 
						|
const VIEWPORT_CENTER = 'viewport_center';
 | 
						|
 | 
						|
// popover orientation can optionally be fixed here (property: popover),
 | 
						|
// otherwise popovers.compute_placement is used to compute orientation
 | 
						|
const HOTSPOT_LOCATIONS = new Map([
 | 
						|
    ["intro_reply", {
 | 
						|
        element: '.selected_message .messagebox-content',
 | 
						|
        offset_x: 0.85,
 | 
						|
        offset_y: 0.7,
 | 
						|
        popover: BOTTOM,
 | 
						|
    }],
 | 
						|
    ["intro_streams", {
 | 
						|
        element: '#streams_header .sidebar-title',
 | 
						|
        offset_x: 1.35,
 | 
						|
        offset_y: 0.39,
 | 
						|
    }],
 | 
						|
    ["intro_topics", {
 | 
						|
        element: '.topic-name',
 | 
						|
        offset_x: 0.8,
 | 
						|
        offset_y: 0.39,
 | 
						|
    }],
 | 
						|
    ["intro_gear", {
 | 
						|
        element: '#settings-dropdown',
 | 
						|
        offset_x: -0.4,
 | 
						|
        offset_y: 1.2,
 | 
						|
        popover: LEFT_BOTTOM,
 | 
						|
    }],
 | 
						|
    ["intro_compose", {
 | 
						|
        element: '#left_bar_compose_stream_button_big',
 | 
						|
        offset_x: 0,
 | 
						|
        offset_y: 0,
 | 
						|
    }],
 | 
						|
]);
 | 
						|
 | 
						|
// popover illustration url(s)
 | 
						|
const WHALE = '/static/images/hotspots/whale.svg';
 | 
						|
 | 
						|
exports.post_hotspot_as_read = function (hotspot_name) {
 | 
						|
    channel.post({
 | 
						|
        url: '/json/users/me/hotspots',
 | 
						|
        data: { hotspot: JSON.stringify(hotspot_name) },
 | 
						|
        error: function (err) {
 | 
						|
            blueslip.error(err.responseText);
 | 
						|
        },
 | 
						|
    });
 | 
						|
};
 | 
						|
 | 
						|
function place_icon(hotspot) {
 | 
						|
    const element = $(hotspot.location.element);
 | 
						|
    const icon = $('#hotspot_' + hotspot.name + '_icon');
 | 
						|
 | 
						|
    if (element.length === 0 || element.css('display') === 'none' ||
 | 
						|
        !element.is(':visible') || element.is(':hidden')) {
 | 
						|
        icon.css('display', 'none');
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
 | 
						|
    const offset = {
 | 
						|
        top: element.outerHeight() * hotspot.location.offset_y,
 | 
						|
        left: element.outerWidth() * hotspot.location.offset_x,
 | 
						|
    };
 | 
						|
    const client_rect = element.get(0).getBoundingClientRect();
 | 
						|
    const placement = {
 | 
						|
        top: client_rect.top + offset.top,
 | 
						|
        left: client_rect.left + offset.left,
 | 
						|
    };
 | 
						|
    icon.css('display', 'block');
 | 
						|
    icon.css(placement);
 | 
						|
    return true;
 | 
						|
}
 | 
						|
 | 
						|
function place_popover(hotspot) {
 | 
						|
    if (!hotspot.location.element) {
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    const popover_width = $('#hotspot_' + hotspot.name + '_overlay .hotspot-popover').outerWidth();
 | 
						|
    const popover_height = $('#hotspot_' + hotspot.name + '_overlay .hotspot-popover').outerHeight();
 | 
						|
    const el_width = $(hotspot.location.element).outerWidth();
 | 
						|
    const el_height = $(hotspot.location.element).outerHeight();
 | 
						|
    const arrow_offset = 20;
 | 
						|
 | 
						|
    let popover_offset;
 | 
						|
    let arrow_placement;
 | 
						|
    const orientation = hotspot.location.popover ||
 | 
						|
        popovers.compute_placement(
 | 
						|
            $(hotspot.location.element),
 | 
						|
            popover_height,
 | 
						|
            popover_width,
 | 
						|
            false
 | 
						|
        );
 | 
						|
 | 
						|
    switch (orientation) {
 | 
						|
    case TOP:
 | 
						|
        popover_offset = {
 | 
						|
            top: -(popover_height + arrow_offset),
 | 
						|
            left: el_width / 2 - popover_width / 2,
 | 
						|
        };
 | 
						|
        arrow_placement = 'bottom';
 | 
						|
        break;
 | 
						|
 | 
						|
    case LEFT:
 | 
						|
        popover_offset = {
 | 
						|
            top: el_height / 2 - popover_height / 2,
 | 
						|
            left: -(popover_width + arrow_offset),
 | 
						|
        };
 | 
						|
        arrow_placement = 'right';
 | 
						|
        break;
 | 
						|
 | 
						|
    case BOTTOM:
 | 
						|
        popover_offset = {
 | 
						|
            top: el_height + arrow_offset,
 | 
						|
            left: el_width / 2 - popover_width / 2,
 | 
						|
        };
 | 
						|
        arrow_placement = 'top';
 | 
						|
        break;
 | 
						|
 | 
						|
    case RIGHT:
 | 
						|
        popover_offset = {
 | 
						|
            top: el_height / 2 - popover_height / 2,
 | 
						|
            left: el_width + arrow_offset,
 | 
						|
        };
 | 
						|
        arrow_placement = 'left';
 | 
						|
        break;
 | 
						|
 | 
						|
    case LEFT_BOTTOM:
 | 
						|
        popover_offset = {
 | 
						|
            top: 0,
 | 
						|
            left: -(popover_width + arrow_offset / 2),
 | 
						|
        };
 | 
						|
        arrow_placement = '';
 | 
						|
        break;
 | 
						|
 | 
						|
    case VIEWPORT_CENTER:
 | 
						|
        popover_offset = {
 | 
						|
            top: el_height / 2,
 | 
						|
            left: el_width / 2,
 | 
						|
        };
 | 
						|
        arrow_placement = '';
 | 
						|
        break;
 | 
						|
 | 
						|
    default:
 | 
						|
        blueslip.error(
 | 
						|
            'Invalid popover placement value for hotspot \'' +
 | 
						|
                hotspot.name + '\''
 | 
						|
        );
 | 
						|
        break;
 | 
						|
    }
 | 
						|
 | 
						|
    // position arrow
 | 
						|
    arrow_placement = 'arrow-' + arrow_placement;
 | 
						|
    $('#hotspot_' + hotspot.name + '_overlay .hotspot-popover')
 | 
						|
        .removeClass('arrow-top arrow-left arrow-bottom arrow-right')
 | 
						|
        .addClass(arrow_placement);
 | 
						|
 | 
						|
    // position popover
 | 
						|
    let popover_placement;
 | 
						|
    if (orientation === VIEWPORT_CENTER) {
 | 
						|
        popover_placement = {
 | 
						|
            top: '45%',
 | 
						|
            left: '50%',
 | 
						|
            transform: 'translate(-50%, -50%)',
 | 
						|
        };
 | 
						|
    } else {
 | 
						|
        const client_rect = $(hotspot.location.element).get(0).getBoundingClientRect();
 | 
						|
        popover_placement = {
 | 
						|
            top: client_rect.top + popover_offset.top,
 | 
						|
            left: client_rect.left + popover_offset.left,
 | 
						|
            transform: '',
 | 
						|
        };
 | 
						|
    }
 | 
						|
 | 
						|
    $('#hotspot_' + hotspot.name + '_overlay .hotspot-popover')
 | 
						|
        .css(popover_placement);
 | 
						|
}
 | 
						|
 | 
						|
function insert_hotspot_into_DOM(hotspot) {
 | 
						|
    if (hotspot.name === "intro_reply") {
 | 
						|
        $('#bottom_whitespace').append(render_intro_reply_hotspot({}));
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    const hotspot_overlay_HTML = render_hotspot_overlay({
 | 
						|
        name: hotspot.name,
 | 
						|
        title: hotspot.title,
 | 
						|
        description: hotspot.description,
 | 
						|
        img: WHALE,
 | 
						|
    });
 | 
						|
 | 
						|
    const hotspot_icon_HTML =
 | 
						|
        '<div class="hotspot-icon" id="hotspot_' + hotspot.name + '_icon">' +
 | 
						|
            '<span class="dot"></span>' +
 | 
						|
            '<span class="pulse"></span>' +
 | 
						|
            '<div class="bounce"><span class="bounce-icon">?</span></div>' +
 | 
						|
        '</div>';
 | 
						|
 | 
						|
    setTimeout(function () {
 | 
						|
        $('body').prepend(hotspot_icon_HTML);
 | 
						|
        $('body').prepend(hotspot_overlay_HTML);
 | 
						|
        if (place_icon(hotspot)) {
 | 
						|
            place_popover(hotspot);
 | 
						|
        }
 | 
						|
 | 
						|
        // reposition on any event that might update the UI
 | 
						|
        ['resize', 'scroll', 'onkeydown', 'click'].forEach(function (event_name) {
 | 
						|
            window.addEventListener(event_name, _.debounce(function () {
 | 
						|
                if (place_icon(hotspot)) {
 | 
						|
                    place_popover(hotspot);
 | 
						|
                }
 | 
						|
            }, 10), true);
 | 
						|
        });
 | 
						|
    }, hotspot.delay * 1000);
 | 
						|
}
 | 
						|
 | 
						|
exports.is_open = function () {
 | 
						|
    return $('.hotspot.overlay').hasClass('show');
 | 
						|
};
 | 
						|
 | 
						|
exports.close_hotspot_icon = function (elem) {
 | 
						|
    $(elem).animate({ opacity: 0 }, {
 | 
						|
        duration: 300,
 | 
						|
        done: function () {
 | 
						|
            $(elem).css({ display: 'none' });
 | 
						|
        }.bind(elem),
 | 
						|
    });
 | 
						|
};
 | 
						|
 | 
						|
function close_read_hotspots(new_hotspots) {
 | 
						|
    const unwanted_hotspots = _.difference(
 | 
						|
        Array.from(HOTSPOT_LOCATIONS.keys()),
 | 
						|
        new_hotspots.map(hotspot => hotspot.name)
 | 
						|
    );
 | 
						|
 | 
						|
    for (const hotspot_name of unwanted_hotspots) {
 | 
						|
        exports.close_hotspot_icon($('#hotspot_' + hotspot_name + '_icon'));
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
exports.load_new = function (new_hotspots) {
 | 
						|
    close_read_hotspots(new_hotspots);
 | 
						|
    new_hotspots.forEach(function (hotspot) {
 | 
						|
        hotspot.location = HOTSPOT_LOCATIONS.get(hotspot.name);
 | 
						|
    });
 | 
						|
    new_hotspots.forEach(insert_hotspot_into_DOM);
 | 
						|
};
 | 
						|
 | 
						|
exports.initialize = function () {
 | 
						|
    exports.load_new(page_params.hotspots);
 | 
						|
};
 | 
						|
 | 
						|
window.hotspots = exports;
 |