<commit_before>odoo.define('web_tour.TourManager', function(require) {
"use strict";

var core = require('web.core');
var local_storage = require('web.local_storage');
var Model = require('web.Model');
var session = require('web.session');
var Tip = require('web_tour.Tip');

var _t = core._t;

var RUNNING_TOUR_TIMEOUT = 3000;

function getStepKey(name) {
    return 'tour_' + name + '_step';
}
function getRunningKey() {
    return 'running_tour';
}

return core.Class.extend({
    init: function(consumed_tours) {
        this.$body = $('body');
        this.active_tooltips = {};
        this.tours = {};
        this.consumed_tours = consumed_tours || [];
        this.running_tour = local_storage.getItem(getRunningKey());
        this.TourModel = new Model('web_tour.tour');
        this.edition = (_.last(session.server_version_info) === 'e') ? 'enterprise' : 'community';
    },
    /**
     * Registers a tour described by the following arguments (in order)
     * @param [String] tour's name
     * @param [Object] dict of options (optional), available options are:
     *   test [Boolean] true if the tour is dedicated to tests (it won't be enabled by default)
     *   skip_enabled [Boolean] true to add a link to consume the whole tour in its tips
     *   url [String] the url to load when manually running the tour
     * @param [Array] dict of steps, each step being a dict containing a tip description
     */
    register: function() {
        var self = this;
        var args = Array.prototype.slice.call(arguments);
        var last_arg = args[args.length - 1];
        var name = args[0];
        if (this.tours[name]) {
            console.warn(_.str.sprintf(_t("Tour %s is already defined"), name));
            return;
        }
        var options = args.length === 2 ? {} : args[1];
        var steps = last_arg instanceof Array ? last_arg : [last_arg];
        var tour = {
            name: name,
            current_step: parseInt(local_storage.getItem(getStepKey(name))) || 0,
            steps: _.filter(steps, function (step) {
                return !step.edition || step.edition === self.edition;
            }),
            url: options.url,
            test: options.test,
        };
        if (options.skip_enabled) {
            tour.skip_link = '<p><span class="o_skip_tour">' + _t('Skip tour') + '</span></p>';
            tour.skip_handler = function (tip) {
                this._deactivate_tip(tip);
                this._consume_tour(name);
            };
        }
        this.tours[name] = tour;
        if (name === this.running_tour || (!tour.test && !_.contains(this.consumed_tours, name))) {
            this.active_tooltips[name] = steps[tour.current_step];
        }
    },
    run: function(tour_name) {
        if (this.running_tour) {
            console.warn(_.str.sprintf(_t("Killing tour %s"), tour_name));
            this._deactivate_tip(this.active_tooltips[tour_name]);
            this._consume_tour(tour_name);
            return;
        }
        var tour = this.tours[tour_name];
        if (!tour) {
            console.warn(_.str.sprintf(_t("Unknown Tour %s"), name));
            return;
        }
        console.log(_.str.sprintf(_t("Running tour %s"), tour_name));
        local_storage.setItem(getRunningKey(), tour_name);
        if (tour.url) {
            window.location = tour.url;
        }
        this.running_tour = tour_name;
        this.active_tooltips[tour_name] = tour.steps[0];
        this._set_running_tour_timeout(tour_name, tour.steps[0]);
        this.update();
    },
    /**
     * Checks for tooltips to activate (only from the running tour if there is one, from all
     * active tours otherwise). Should be called each time the DOM changes.
     */
    update: function (tour_name) {
        this.$modal_displayed = $('.modal:visible').last();
        tour_name = this.running_tour || tour_name;
        if (tour_name) {
            this._check_for_tooltip(this.active_tooltips[tour_name], tour_name);
        } else {
            _.each(this.active_tooltips, this._check_for_tooltip.bind(this));
        }
    },
    _check_for_tooltip: function (tip, tour_name) {
        var $trigger;
        if (this.$modal_displayed.length) {
            $trigger = this.$modal_displayed.find(tip.trigger);
        } else {
            $trigger = $(tip.trigger);
        }
        $trigger = $trigger.filter(':visible').first();
        var extra_trigger = tip.extra_trigger ? $(tip.extra_trigger).filter(':visible').length : true;
        var triggered = $trigger.length && extra_trigger;
        if (triggered) {
            if (!tip.widget) {
                this._activate_tip(tip, tour_name, $trigger);
            } else {
                tip.widget.update($trigger);
            }
        } else {
            this._deactivate_tip(tip);
        }
    },
    _activate_tip: function(tip, tour_name, $anchor) {
        var tour = this.tours[tour_name];
        var tip_info = tip;
        if (tour.skip_link) {
            tip_info = _.extend(_.omit(tip_info, 'content'), {
                content: tip.content + tour.skip_link,
                event_handlers: [{
                    event: 'click',
                    selector: '.o_skip_tour',
                    handler: tour.skip_handler.bind(this, tip),
                }],
            });
        }<commit_msg>[FIX] web_tour: properly filter on visible elements<commit_after>odoo.define('web_tour.TourManager', function(require) {
"use strict";

var core = require('web.core');
var local_storage = require('web.local_storage');
var Model = require('web.Model');
var session = require('web.session');
var Tip = require('web_tour.Tip');

var _t = core._t;

var RUNNING_TOUR_TIMEOUT = 3000;

function getStepKey(name) {
    return 'tour_' + name + '_step';
}
function getRunningKey() {
    return 'running_tour';
}

return core.Class.extend({
    init: function(consumed_tours) {
        this.$body = $('body');
        this.active_tooltips = {};
        this.tours = {};
        this.consumed_tours = consumed_tours || [];
        this.running_tour = local_storage.getItem(getRunningKey());
        this.TourModel = new Model('web_tour.tour');
        this.edition = (_.last(session.server_version_info) === 'e') ? 'enterprise' : 'community';
    },
    /**
     * Registers a tour described by the following arguments (in order)
     * @param [String] tour's name
     * @param [Object] dict of options (optional), available options are:
     *   test [Boolean] true if the tour is dedicated to tests (it won't be enabled by default)
     *   skip_enabled [Boolean] true to add a link to consume the whole tour in its tips
     *   url [String] the url to load when manually running the tour
     * @param [Array] dict of steps, each step being a dict containing a tip description
     */
    register: function() {
        var self = this;
        var args = Array.prototype.slice.call(arguments);
        var last_arg = args[args.length - 1];
        var name = args[0];
        if (this.tours[name]) {
            console.warn(_.str.sprintf(_t("Tour %s is already defined"), name));
            return;
        }
        var options = args.length === 2 ? {} : args[1];
        var steps = last_arg instanceof Array ? last_arg : [last_arg];
        var tour = {
            name: name,
            current_step: parseInt(local_storage.getItem(getStepKey(name))) || 0,
            steps: _.filter(steps, function (step) {
                return !step.edition || step.edition === self.edition;
            }),
            url: options.url,
            test: options.test,
        };
        if (options.skip_enabled) {
            tour.skip_link = '<p><span class="o_skip_tour">' + _t('Skip tour') + '</span></p>';
            tour.skip_handler = function (tip) {
                this._deactivate_tip(tip);
                this._consume_tour(name);
            };
        }
        this.tours[name] = tour;
        if (name === this.running_tour || (!tour.test && !_.contains(this.consumed_tours, name))) {
            this.active_tooltips[name] = steps[tour.current_step];
        }
    },
    run: function(tour_name) {
        if (this.running_tour) {
            console.warn(_.str.sprintf(_t("Killing tour %s"), tour_name));
            this._deactivate_tip(this.active_tooltips[tour_name]);
            this._consume_tour(tour_name);
            return;
        }
        var tour = this.tours[tour_name];
        if (!tour) {
            console.warn(_.str.sprintf(_t("Unknown Tour %s"), name));
            return;
        }
        console.log(_.str.sprintf(_t("Running tour %s"), tour_name));
        local_storage.setItem(getRunningKey(), tour_name);
        if (tour.url) {
            window.location = tour.url;
        }
        this.running_tour = tour_name;
        this.active_tooltips[tour_name] = tour.steps[0];
        this._set_running_tour_timeout(tour_name, tour.steps[0]);
        this.update();
    },
    /**
     * Checks for tooltips to activate (only from the running tour if there is one, from all
     * active tours otherwise). Should be called each time the DOM changes.
     */
    update: function (tour_name) {
        this.$modal_displayed = $('.modal:visible').last();
        tour_name = this.running_tour || tour_name;
        if (tour_name) {
            this._check_for_tooltip(this.active_tooltips[tour_name], tour_name);
        } else {
            _.each(this.active_tooltips, this._check_for_tooltip.bind(this));
        }
    },
    _check_for_tooltip: function (tip, tour_name) {
        var $trigger;
        if (this.$modal_displayed.length) {
            $trigger = this.$modal_displayed.find(tip.trigger);
        } else {
            $trigger = $(tip.trigger);
        }
        $trigger = get_first_visible_element($trigger);
        var extra_trigger = tip.extra_trigger ? get_first_visible_element($(tip.extra_trigger)).length : true;
        var triggered = $trigger.length && extra_trigger;
        if (triggered) {
            if (!tip.widget) {
                this._activate_tip(tip, tour_name, $trigger);
            } else {
                tip.widget.update($trigger);
            }
        } else {
            this._deactivate_tip(tip);
        }

        function get_first_visible_element($elements) {
            for (var i = 0 ; i < $elements.length ; i++) {
                var $elem = $elements.eq(i);
                if ($elem.is(":visible")) {
                    var $i = $elem;
                    while ($i.css("visibility") !== "hidden") {
                        $i = $i.parent();
                        if ($i.is("html")) {
                            return $elem;
                        }
                    }
                }
            }
            return $();
        }
    },
    _activate_tip: function(tip, tour_name, $anchor) {
        var tour = this.tours[tour_name];
        var tip_info = tip;
        if (tour.skip_link) {
            tip_info = _.extend(_.omit(tip_info, 'content'), {
                content: tip.content + tour.skip_link,
                event_handlers: [{
                    event: 'click',
                    selector: '.o_skip_tour',
                    handler: tour.skip_handler.bind(this, tip),
                }],
            });
        }
{'fraction_non_alphanumeric': 0.13116057233704292, 'fraction_numerical': 0.002031443207913796, 'mean_word_length': 2.042999193765117, 'pattern_counts': {'":': 1, '<': 12, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5207594', 'n_tokens_mistral': 3408, 'n_tokens_neox': 3280, 'n_words': 739}