resources/web/docs_js/index-v1.js (431 lines of code) (raw):
import AlternativeSwitcher from "./components/alternative_switcher.js";
import ConsoleWidget from "./components/console_widget.jsx";
import FeedbackModal from './components/feedback_modal.jsx';
import FeedbackWidget from './components/feedback_widget.jsx';
import Modal from "./components/modal.js";
import mount from "./components/mount.js";
import {switchTabs} from "./components/tabbed_widget.js";
import {Cookies, $} from "./deps.js";
import {lang_strings} from "./localization.js";
import store from "./store.js";
import * as utils from "./utils.js";
import PR from "../lib/prettify/prettify.js";
import "./prettify/lang-asciidoc.js";
import "./prettify/lang-console.js";
import "../lib/prettify/lang-esql.js";
import "../lib/prettify/lang-sql.js";
import "../lib/prettify/lang-yaml.js";
// Add support for <details> in IE and the like
import "../../../../../node_modules/details-polyfill";
// Add support for URLSearchParams Web API in IE
import "../../../../../node_modules/url-search-params-polyfill";
export function init_landing_page() {
// Because of the nature of the injected links, we need to adjust the layout to
// Fit into two columns on the landing page.
// Select all top-level h3 elements within the #content div
$('.docs-landing div#content > h3').each(function() {
var $siblingDiv = $(this).next('div.ulist.itemizedlist');
// Wrap the h3 and its sibling div in a div with class docs-link-section
$(this).add($siblingDiv).wrapAll('<div class="docs-link-section"></div>');
});
// Select the last .docs-link-section
var $lastDocsLinkSection = $('.docs-link-section:last');
// Remove it from its current position
$lastDocsLinkSection.detach();
// Append it outside of the div#content element
$lastDocsLinkSection.addClass('legacy-docs hidden').insertAfter('div#content');
$lastDocsLinkSection.find('h3').append('<span class="toggle-icon">▼</span>');
// Click handler to toggle visibility
$lastDocsLinkSection.find('h3').on('click', function() {
$lastDocsLinkSection.toggleClass('hidden');
});
// Move "need help" section to the bottom of the page
$('#bottomContent').insertAfter($lastDocsLinkSection).show();
}
// Vocab:
// TOC = table of contents
// OTP = on this page
export function init_headers(sticky_content, lang_strings) {
// Add on-this-page block
var this_page = $('<div id="this_page"></div>').prependTo(sticky_content);
this_page.append('<p id="otp" class="aside-heading">' + lang_strings('On this page') + '</p>');
var ul = $('<ul></ul>').appendTo(this_page);
var items = 0;
// Get all headings inside the main body of the doc
$('div#content a[id]:not([href])').each(
function(i) {
// Make headers into real links for permalinks
this.href = '#' + this.id;
// Extract on-this-page headers, without embedded links
var title_container = $(this).parent('h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11,h12').clone();
if (title_container.length > 0) {
// Assume initial heading is an H1, but adjust if it's not
let hLevel;
if ($(this).parent().is("h2")){
hLevel = 0;
} else if ($(this).parent().is("h3")){
hLevel = 1;
} else if ($(this).parent().is("h4")){
hLevel = 2;
} else if ($(this).parent().is("h5,h6,h7,h8,h9,h10,h11,h12")) {
hLevel = null
}
// Build list items for all headings except the page title
if (0 < items++) {
title_container.find('a,.added,.coming,.deprecated,.experimental')
.remove();
var text = title_container.html();
if (hLevel !== null) {
const li = '<li id="otp-text-' + i + '" class="heading-level-' + hLevel + '"><a href="#' + this.id + '">' + text + '</a></li>';
ul.append(li);
}
}
}
});
if (items < 2) {
this_page.remove();
}
}
export function init_console_widgets() {
$('div.console_widget').each(function() {
const div = $(this),
snippet = div.attr('data-snippet'),
consoleText = div.prev().text() + '\n',
langs = div.attr("class").split(" ").filter(c => c.startsWith("has-")).map(function(string) { return string.substring(4) });
return mount(div, ConsoleWidget, {setting: "console",
url_label: 'Console URL',
view_in_text: 'Try in Elastic',
configure_text: 'Configure Console URL',
addPretty: true,
consoleText,
snippet,
langs});
});
}
export function init_feedback_widget() {
mount($('#feedbackWidgetContainer'), FeedbackWidget);
$('.feedbackButton').click(function () {
const isLiked = $(this).hasClass('feedbackLiked');
$(this).addClass('isPressed');
mount($('#feedbackModalContainer'), FeedbackModal, { isLiked: isLiked });
});
}
export function init_sense_widgets() {
$('div.sense_widget').each(function() {
const div = $(this),
snippet = div.attr('data-snippet'),
consoleText = div.prev().text() + '\n';
return mount(div, ConsoleWidget, {setting: "sense",
url_label: 'Sense URL',
view_in_text: 'View in Sense',
configure_text: 'Configure Sense URL',
addPretty: true,
consoleText,
snippet});
});
}
function init_kibana_widgets() {
$('div.kibana_widget').each(function() {
const div = $(this),
snippet = div.attr('data-snippet'),
consoleText = div.prev().text() + '\n';
return mount(div, ConsoleWidget, {setting: "kibana",
isKibana: true,
url_label: 'Enter the URL of Kibana',
configure_text: 'Configure Kibana URL',
consoleText,
snippet});
});
}
function init_toc(lang_strings) {
var title = $('#book_title');
// Make li elements in toc collapsible
$('div.toc li ul').each(function() {
var li = $(this).parent();
li.addClass('collapsible').children('span').click(function() {
if (li.hasClass('show')) {
li.add(li.find('li.show')).removeClass('show');
if (title.hasClass('show')) {
title.removeClass('show');
}
} else {
li.parents('div.toc,li').first().find('li.show').removeClass('show');
li.addClass('show');
}
});
});
// Make book title in toc collapsible
if ($('.collapsible').length > 0) {
title.addClass('collapsible').click(function() {
if (title.hasClass('show')) {
title.removeClass('show');
title.parent().find('.show').removeClass('show');
} else {
title.addClass('show');
title.parent().find('.collapsible').addClass('show');
}
});
}
// Clicking links or the version selector shouldn't fold/expand
$('div.toc a, #book_title select').click(function(e) {
e.stopPropagation();
});
// Setup version selector
var v_selected = title.find('select option:selected');
title
.find('select')
.change(function(e) {
var version = $(e.target).find('option:selected').val();
if (version === "other") {
$("#other_versions").show();
$("#live_versions").hide();
return;
}
utils.get_current_page_in_version(version).fail(function() {
v_selected.attr('selected', 'selected');
alert(lang_strings('This page is not available in the docs for version:')
+ version);
});
});
}
// In the OTP, highlight the heading of the section that is
// currently visible on the page.
// If more than one is visible, highlight the heading for the
// section that is higher on the page.
function highlight_otp() {
let visibleHeadings = []
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
const id = entry.target.getAttribute('id');
const element = document.querySelector(`#sticky_content #this_page a[href="#${id}"]`);
const itemId = $(element).parent().attr('id')
// All heading elements have an `entry` (even the title).
// The title does not exist in the OTP, so we must exclude it.
// Checking for the existence of `itemId` ensures we don't parse elements that don't exist.
if (itemId){
const itemNumber = parseInt(itemId.match(/\d+/)[0], 10);
if (entry.intersectionRatio > 0){
visibleHeadings.push(itemNumber);
} else {
const position = visibleHeadings.indexOf(itemNumber);
visibleHeadings.splice(position, 1)
}
if (visibleHeadings.length > 0) {
visibleHeadings.sort((a, b) => a - b)
// Remove existing active classes
$('a.active').removeClass("active");
// Add active class to the first visible heading
$('#otp-text-' + visibleHeadings[0] + ' > a').addClass('active')
}
}
})
})
document.querySelectorAll('div#content a[id]').forEach((heading, i) => {
// Skip the first heading since it's not visible
if (i === 0) return
observer.observe(heading);
})
}
function getUtm() {
const qs = new Proxy(new URLSearchParams(window.location.search), {
get: (searchParams, prop) => searchParams.get(prop),
})
return {
'utm_source': qs['utm_source'],
'utm_medium': qs['utm_medium'],
'utm_campaign': qs['utm_campaign'],
'utm_content': qs['utm_content'],
'utm_term': qs['utm_term'],
'utm_id': qs['utm_id'],
}
}
function getCookie(cookieName) {
let cookie = document.cookie
.split('; ')
.find(row => row.startsWith(cookieName + '='));
if (cookie == undefined) {
return undefined
}
return cookie.split('=')[1]
}
function getEuid() {
return getCookie('euid')
}
// Main function, runs on DOM ready
$(function() {
var lang = $('section#guide[lang]').attr('lang') || 'en';
const default_kibana_url = 'http://localhost:5601',
default_base_path = '/zzz', // Since the original implementation, the base path was added and most users use it.
default_console_url = default_kibana_url + default_base_path + '/app/kibana#/dev_tools/console',
default_sense_url = default_kibana_url + '/app/sense/',
default_ess_url = 'http://localhost:5601', // localhost is wrong, but we'll enhance this later
default_ece_url = 'http://localhost:5601',
base_url = utils.get_base_url(window.location.href),
LangStrings = lang_strings(lang);
// Capturing the various global variables into the store
const initialStoreState = {
settings: {
language: lang,
langStrings: LangStrings,
baseUrl: base_url,
kibana_url: Cookies.get("kibana_url") || default_kibana_url,
kibana_curl_host: Cookies.get("kibana_curl_host") || "localhost:5601",
kibana_curl_user: Cookies.get("kibana_curl_user"),
kibana_curl_password: "$KIBANAPASS",
console_url: Cookies.get("console_url") || default_console_url,
console_curl_host: Cookies.get("console_curl_host") || "localhost:9200",
console_curl_user: Cookies.get("console_curl_user"),
console_curl_password: "$ESPASS",
sense_url: Cookies.get("sense_url") || default_sense_url,
sense_curl_host: Cookies.get("sense_curl_host") || "localhost:9200",
sense_curl_user: Cookies.get("sense_curl_user"),
sense_curl_password: "$ESPASS",
ess_url: Cookies.get("ess_url") || default_ess_url,
ess_curl_host: Cookies.get("ess_curl_host") || "localhost:5601",
ess_curl_user: Cookies.get("ess_curl_user"),
ess_curl_password: "$CLOUD_PASS",
ece_url: Cookies.get("ece_url") || default_ece_url,
ece_curl_host: Cookies.get("ece_curl_host") || "localhost:5601",
ece_curl_user: Cookies.get("ece_curl_user"),
ece_curl_password: "$ECE_PASS",
consoleAlternative: Cookies.get('consoleAlternative') || "console",
},
/*
* Grab the initial state that we know how to deal with from the page.
* Rather than grab *everything* we grab the keys we can reduce to prevent
* things from falling over when an out of date version of the js sees new
* initial state. This wouldn't be a thing if we could bust the cache at
* will but, at this point, we can't.
*/
alternatives: window.initial_state.alternatives,
};
// first call to store initializes it
store(initialStoreState);
// One modal component for N mini-apps
mount($('body'), Modal);
AlternativeSwitcher(store());
// Get all headings inside the main body of the doc
const allHeadings = $('div#content').find('h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11,h12')
let allLevels = []
// Create a list of all heading levels used on the page
allHeadings.each(function(index) {
// Don't include the first heading because that's the title
if (index === 0) return;
if (!allLevels.includes($(this).prop('nodeName'))) allLevels.push($(this).prop('nodeName'));
})
// Update the heading level to be incremental
// (i.e. the first heading after the title should be an h2 and
// then deeper levels should be adjusted so they are incremental)
allHeadings.each(function(index) {
const currentHeading = $(this)
const contents = currentHeading.prop('innerHTML')
// Don't include the first heading because that's the
// title and we always want that to be an h1
if (index > 0) {
if (allLevels[0] && ($(this).prop('nodeName') === allLevels[0])) {
$(this).replaceWith(`<h2>${contents}</h2>`);
}
if (allLevels[1] && ($(this).prop('nodeName') === allLevels[1])) {
$(this).replaceWith(`<h3>${contents}</h3>`);
}
if (allLevels[2] && ($(this).prop('nodeName') === allLevels[2])) {
$(this).replaceWith(`<h4>${contents}</h4>`);
}
if (allLevels[3] && ($(this).prop('nodeName') === allLevels[3])) {
$(this).replaceWith(`<h5>${contents}</h5>`);
}
if (allLevels[4] && ($(this).prop('nodeName') === allLevels[4])) {
$(this).replaceWith(`<h6>${contents}</h6>`);
}
}
})
// If breadcrumbs contain a dropdown (e.g. APM, ECS Logging)
// handle interaction with the dropdown
if ($('#related-products')) {
// Select-type element used to reveal options
const dropDownAnchor = $('#related-products > .dropdown-anchor')
// Popover-type element containing options
const dropDownContent = $('#related-products > .dropdown-content')
// Toggle the visibility of the popover on click
dropDownAnchor.click(function (e) {
e.preventDefault();
dropDownContent.toggleClass('show')
});
// Toggle the visibility of the popover on enter
dropDownAnchor.keypress(function (e) {
if (e.which == 13) {
dropDownContent.toggleClass('show')
}
});
// Close the popover when clicking outside it
$(document).mouseup(function(e) {
if (
dropDownContent.hasClass("show")
&& !dropDownAnchor.is(e.target)
&& !dropDownContent.is(e.target)
&& dropDownContent.has(e.target).length === 0
) {
dropDownContent.removeClass("show")
}
})
// Bold the item in the popover that represents
// the current book
const currentBookTitle = dropDownAnchor.text()
const items = dropDownContent.find("li")
items.each(function(i) {
if (items[i].innerText === currentBookTitle) {
const link = items[i].children[0]
link.style.fontWeight = 700
}
})
}
// Move rtp container to top right and make visible
var sticky_content = $('#sticky_content');
// Left column that contains the TOC
var left_col = $('#left_col');
// Middle column that contains the main content
var middle_col = $('#middle_col');
// Right column that contains the OTP and demand gen content
var right_col = $('#right_col');
// Empty column below TOC on small screens so the demand gen content can be positioned under the main content
var bottom_left_col = $('#bottom_left_col');
$('.page_header > a[href="../current/index.html"]').click(function(e) {
e.preventDefault();
utils.get_current_page_in_version('current').fail(function() {
location.href = "../current/index.html"
});
});
// Enable Sense widget
init_sense_widgets();
init_console_widgets();
init_kibana_widgets();
init_feedback_widget();
init_landing_page();
$("div.ess_widget").each(function() {
const div = $(this),
snippet = div.attr('data-snippet'),
consoleText = div.prev().text() + '\n';
return mount(div, ConsoleWidget, {
setting: "ess",
url_label: 'Enter the endpoint URL of the Elasticsearch Service',
configure_text: 'Configure the Elasticsearch Service endpoint URL',
consoleText,
snippet
});
});
$("div.ece_widget").each(function() {
const div = $(this),
snippet = div.attr('data-snippet'),
consoleText = div.prev().text() + '\n';
return mount(div, ConsoleWidget, {
setting: "ece",
url_label: 'Enter the endpoint URL of Elastic Cloud Enterprise',
configure_text: 'Configure the Elastic Cloud Enterprise endpoint URL',
consoleText,
snippet
});
});
$('div.console_code_copy').each(function () {
const $copyButton = $(this);
const langText = $copyButton.next().text();
$copyButton.on('click', function () {
utils.copyText(langText, lang_strings);
$copyButton.addClass('copied');
setTimeout(function () {
$copyButton.removeClass('copied')
}, 3000);
});
});
var div = $('div.toc');
// Fetch toc.html unless there is already a .toc on the page
if (div.length == 0) {
var url = location.href.replace(/[^\/]+$/, 'toc.html');
var toc = $.get(url, {}, function(data) {
left_col.append(data);
init_toc(LangStrings);
utils.open_current(location.pathname);
}).always(function() {
init_headers(sticky_content, LangStrings);
highlight_otp();
});
} else {
init_toc(LangStrings);
// Style book landing page (no main content, just a TOC and demand gen content)
// Set the width of the left column to zero
left_col.removeClass().addClass('col-0');
bottom_left_col.removeClass().addClass('col-0');
// Set the width of the middle column (containing the TOC) to 9
middle_col.removeClass().addClass('col-12 col-lg-9 guide-section');
// Set the width of the demand gen content to 3
right_col.removeClass().addClass('col-12 col-lg-3 sticky-top-md h-almost-full-lg');
}
PR.prettyPrint();
// Setup hot module replacement for css if we're in dev mode.
if (module.hot) {
var hotcss = document.createElement('script');
hotcss.setAttribute('src', '/guide/static/styles.js');
document.head.appendChild(hotcss);
}
// For the private docs repositories, the edit button is hidden
// unless there is an '?edit' in the query string or hash.
if (new URLSearchParams(window.location.search).has('edit')
|| window.location.hash.indexOf('?edit') > -1) {
$('a.edit_me_private').show();
}
// scroll to selected TOC element; if it doesn't exist yet, wait and try again
// window.width must match the breakpoint of `.sticky-top-md`
if($(window).width() >= 769){
var scrollToSelectedTOC = setInterval(() => {
if ($('.current_page').length) {
// Get scrollable element
var container = document.querySelector("#left_col");
// Get active table of contents element
var activeItem = document.querySelector(".current_page")
// If the top of the active item is out of view (or in the bottom 100px of the visible portion of the TOC)
// scroll so the top of the active item is at the top of the visible portion TOC
if (container.offsetHeight - 100 <= activeItem.offsetTop) {
// Scroll to active item
container.scrollTop = activeItem.offsetTop
}
clearInterval(scrollToSelectedTOC);
}
}, 150);
}
window.dataLayer = window.dataLayer || [];
const titleParams = document.title.split('|')
const pageViewData = {
'event': 'page_view',
'pagePath': window.location.pathname,
'pageURL': window.location.href,
'pageTitle': document.title,
'pageTemplate': '', // ?
'team': 'Docs',
'pageCategory': titleParams[titleParams.length - 2].trim(),
'hostname': window.location.hostname,
'canonicalTag': window.location.protocol + '//' + window.location.hostname + window.location.pathname,
'euid': getEuid(),
'userId': getCookie('userId'),
'hashedIP': getCookie('hashedIp'),
'userAgent': window.navigator.userAgent,
...getUtm()
};
dataLayer.push(pageViewData);
// Test comment used to detect unminifed JS in tests
});
// Tabbed widgets
switchTabs();