From deeb94592cd2acb7c292da78da52a3314f901cfb Mon Sep 17 00:00:00 2001 From: Jason Stirnaman Date: Mon, 4 Nov 2024 21:45:30 -0600 Subject: [PATCH] WIP(api): JavaScript module refactor, API stylesheets and theme switching - Use js.Build in asset pipeline to transpile and tree-shake ESM modules - Reorganize JS from HTML sections into modular ESM (benefts: testing, intellisense, package management, explicit dependencies (imports) - Move some code into Reactish functional components - Light/dark theme switching for Rapidoc - Basic light/dark SCSS setup - Inherits the user's preferred server URL and sets it as the API server URL --- assets/js/FluxGroupKeys.js | 6 + assets/js/Sidebar.js | 5 + assets/js/ThemeStyle.js | 5 + assets/js/api-doc/ApiReferencePage.js | 15 + assets/js/api-doc/index.js | 67 +- assets/js/api-libs.js | 9 + assets/js/code-controls.js | 2 + assets/js/code-placeholders.js | 2 + assets/js/content-interactions.js | 70 +- assets/js/cookies.js | 76 +- assets/js/custom-timestamps.js | 5 + assets/js/datetime.js | 2 + assets/js/docs-themes.js | 12 +- assets/js/feature-callouts.js | 1 + assets/js/flux-group-keys.js | 11 +- assets/js/flux-influxdb-versions.js | 1 + assets/js/home-interactions.js | 2 + assets/js/influxdb-url.js | 11 +- assets/js/js.cookie.js | 165 --- assets/js/keybindings.js | 2 + assets/js/list-filters.js | 2 + assets/js/main.js | 80 +- assets/js/modals.js | 1 + assets/js/notifications.js | 1 + assets/js/page-feedback.js | 1 + assets/js/release-toc.js | 1 + assets/js/scroll.js | 21 + assets/js/search-interactions.js | 22 +- assets/js/sidebar-toggle.js | 10 + assets/js/tabbed-content.js | 62 +- assets/js/v3-wayfinding.js | 113 +- assets/js/version-selector.js | 35 +- .../product-overrides/api-reference.scss | 38 + assets/styles/styles-default.scss | 3 +- assets/styles/themes/_theme-dark.scss | 3 +- assets/styles/themes/_theme-light.scss | 3 +- assets/styles/themes/dark/api-reference.scss | 35 + assets/styles/themes/light/api-reference.scss | 34 + layouts/api/single.html | 24 +- layouts/partials/footer.html | 6 + layouts/partials/footer/javascript.html | 64 +- layouts/partials/footer/v3-wayfinding.html | 8 +- layouts/partials/header.html | 19 +- layouts/partials/header/javascript.html | 9 - .../partials/header/search-attributes.html | 8 +- layouts/partials/header/stylesheets.html | 6 + layouts/partials/sidebar.html | 5 + layouts/partials/sidebar/sidebar-toggle.html | 2 +- layouts/partials/topnav.html | 6 +- layouts/partials/topnav/version-selector.html | 5 + layouts/shortcodes/flux/group-key-demo.html | 5 + package.json | 5 +- yarn.lock | 1196 +++++++++-------- 53 files changed, 1257 insertions(+), 1045 deletions(-) create mode 100644 assets/js/FluxGroupKeys.js create mode 100644 assets/js/Sidebar.js create mode 100644 assets/js/ThemeStyle.js create mode 100644 assets/js/api-doc/ApiReferencePage.js delete mode 100644 assets/js/js.cookie.js create mode 100644 assets/js/scroll.js create mode 100644 assets/styles/product-overrides/api-reference.scss create mode 100644 assets/styles/themes/dark/api-reference.scss create mode 100644 assets/styles/themes/light/api-reference.scss diff --git a/assets/js/FluxGroupKeys.js b/assets/js/FluxGroupKeys.js new file mode 100644 index 0000000000..f05f58a179 --- /dev/null +++ b/assets/js/FluxGroupKeys.js @@ -0,0 +1,6 @@ +import { groupData } from './flux-group-keys.js'; + +export default function FluxGroupKeys() { + // Group and render tables on load + document.querySelector(tablesElement).addEventListener('load', groupData()); +}; \ No newline at end of file diff --git a/assets/js/Sidebar.js b/assets/js/Sidebar.js new file mode 100644 index 0000000000..a2c648ab1d --- /dev/null +++ b/assets/js/Sidebar.js @@ -0,0 +1,5 @@ +import { setSidebarState } from './sidebar-toggle.js'; + +export default function Sidebar() { + setSidebarState(); +} \ No newline at end of file diff --git a/assets/js/ThemeStyle.js b/assets/js/ThemeStyle.js new file mode 100644 index 0000000000..9c4aef740a --- /dev/null +++ b/assets/js/ThemeStyle.js @@ -0,0 +1,5 @@ +import { setStyleFromCookie } from './docs-themes.js'; + +export default function ThemeStyle() { + setStyleFromCookie(); +} \ No newline at end of file diff --git a/assets/js/api-doc/ApiReferencePage.js b/assets/js/api-doc/ApiReferencePage.js new file mode 100644 index 0000000000..511a3f3d84 --- /dev/null +++ b/assets/js/api-doc/ApiReferencePage.js @@ -0,0 +1,15 @@ +import { setStyles, setServerUrl, onPreferenceChanged } from './index.js'; + +export default function ApiReferencePage() { + const rapidocEl = document.getElementById('api-doc'); + if(rapidocEl === null) return; + setStyles(rapidocEl); + setServerUrl(rapidocEl); + rapidocEl.loadSpec(JSON.parse(rapidocEl.dataset.openapiSpec)); + rapidocEl.addEventListener('spec-loaded', (e) => {}); + cookieStore.addEventListener('change', (e) => { + if(e.changed) { + onPreferenceChanged(e, rapidocEl); + } + }); +} \ No newline at end of file diff --git a/assets/js/api-doc/index.js b/assets/js/api-doc/index.js index 619603070b..155bb9ac83 100644 --- a/assets/js/api-doc/index.js +++ b/assets/js/api-doc/index.js @@ -1,21 +1,58 @@ import 'rapidoc'; +import { getPreference } from '../cookies.js'; +import { getUrls } from '../influxdb-url.js'; function getUserPreferredUrl() { - const urlName = window.getPreference && window.getPreference('influxdb_url'); - return getUrls()[urlName] || (window.placeholderUrls && window.placeholderUrls['oss']); + const urlName = getPreference('influxdb_url'); + return getUrls()[urlName]; } - -export function apiDocComponent() { - window.addEventListener('DOMContentLoaded', (event) => { - const rapidocEl = document.getElementById('api-doc'); - if(rapidocEl === null) return; - const apiServer = getUserPreferredUrl(); - rapidocEl.addEventListener('spec-loaded', (e) => { - rapidocEl.setApiServer(apiServer); - }); - const spec = JSON.parse(rapidocEl.dataset.openapiSpec); - rapidocEl.loadSpec(spec); - }); + +export function setServerUrl(el) { + const baseUrl = getUserPreferredUrl(); + el.setAttribute('server-url', baseUrl); + el.setApiServer(baseUrl); } -apiDocComponent(); \ No newline at end of file +const darkThemeAttributes = { + 'theme': 'dark', +}; + +const lightThemeAttributes = { + 'theme': 'light', +}; + +export function setStyles(el) { + let theme = getPreference('theme') || 'light'; + theme = theme.replace(/-theme/, ''); + let themeAttributes = { + 'nav-accent-color': "", + 'nav-bg-color': "", + 'nav-hover-bg-color': "", + 'nav-hover-text-color': "", + 'nav-text-color': "", + 'primary-color': "#F63C41", + 'render-style': 'view', + 'show-header': 'false', + 'show-info': 'false', + 'style': 'height:100vh; width:100%', + } + switch (theme) { + case 'light': + themeAttributes = { ...themeAttributes, ...lightThemeAttributes }; + break; + case 'dark': + themeAttributes = { ...themeAttributes, ...darkThemeAttributes }; + break; + } + + for (const [key, value] of Object.entries(themeAttributes)) { + el.setAttribute(key, value); + } +} + +export function onPreferenceChanged(e) { + const rapidocEl = document.getElementById('api-doc'); + if(rapidocEl === null) return; + setStyles(rapidocEl); + setServerUrl(rapidocEl); +} diff --git a/assets/js/api-libs.js b/assets/js/api-libs.js index 7cd5a75739..52f8a599b2 100644 --- a/assets/js/api-libs.js +++ b/assets/js/api-libs.js @@ -1,6 +1,8 @@ //////////////////////////////////////////////////////////////////////////////// ///////////////// Preferred Client Library programming language /////////////// //////////////////////////////////////////////////////////////////////////////// +import { setPreference, getPreference } from './cookies.js'; +import { activateTabs, updateBtnURLs } from './tabbed-content.js'; function getVisitedApiLib () { const path = window.location.pathname.match( @@ -35,3 +37,10 @@ var tab = getApiLibPreference(); selector => activateTabs(selector, tab), updateBtnURLs(tab) ); + +export { + getApiLibPreference, + setApiLibPreference, + getVisitedApiLib, + isApiLib, +}; diff --git a/assets/js/code-controls.js b/assets/js/code-controls.js index 76589de43b..201c2576fc 100644 --- a/assets/js/code-controls.js +++ b/assets/js/code-controls.js @@ -1,3 +1,5 @@ +import $ from 'jquery'; + var codeBlockSelector = ".article--content pre"; var codeBlocks = $(codeBlockSelector); diff --git a/assets/js/code-placeholders.js b/assets/js/code-placeholders.js index 11cc9af274..cef02aa375 100644 --- a/assets/js/code-placeholders.js +++ b/assets/js/code-placeholders.js @@ -1,3 +1,5 @@ +import $ from 'jquery'; + const placeholderWrapper = '.code-placeholder-wrapper'; const placeholderElement = 'var.code-placeholder'; const codePlaceholders = $(placeholderElement); diff --git a/assets/js/content-interactions.js b/assets/js/content-interactions.js index 3a1737f76f..c99be1aeb8 100644 --- a/assets/js/content-interactions.js +++ b/assets/js/content-interactions.js @@ -1,4 +1,28 @@ ///////////////////////////// Make headers linkable ///////////////////////////// +import $ from 'jquery'; +import { scrollToAnchor } from './scroll.js'; + +// Expand accordions on load based on URL anchor +function openAccordionByHash() { + var anchor = window.location.hash; + + function expandElement() { + if ($(anchor).parents('.expand').length > 0) { + return $(anchor).closest('.expand').children('.expand-label'); + } else if ($(anchor).hasClass('expand')){ + return $(anchor).children('.expand-label'); + } + }; + + if (expandElement() != null) { + if (expandElement().children('.expand-toggle').hasClass('open')) {} + else { + expandElement().children('.expand-toggle').trigger('click'); + }; + }; +}; + +function contentInteractions () { var headingWhiteList = $("\ .article--content h2, \ @@ -12,7 +36,7 @@ var headingBlackList = ("\ .influxdbu-banner h4 \ "); -headingElements = headingWhiteList.not(headingBlackList); +const headingElements = headingWhiteList.not(headingBlackList); headingElements.each(function() { function getLink(element) { @@ -33,26 +57,6 @@ var elementWhiteList = [ "a.fullscreen-close" ] -function scrollToAnchor(target) { - var $target = $(target); - if($target && $target.length > 0) { - $('html, body').stop().animate({ - 'scrollTop': ($target.offset().top) - }, 400, 'swing', function () { - window.location.hash = target; - }); - - // Unique accordion functionality - // If the target is an accordion element, open the accordion after scrolling - if ($target.hasClass('expand')) { - if ($(target + ' .expand-label .expand-toggle').hasClass('open')) {} - else { - $(target + '> .expand-label').trigger('click'); - }; - }; - } -} - $('.article a[href^="#"]:not(' + elementWhiteList + ')').click(function (e) { e.preventDefault(); scrollToAnchor(this.hash); @@ -98,25 +102,7 @@ $('.expand-label').click(function() { $(this).next('.expand-content').slideToggle(200) }) -// Expand accordions on load based on URL anchor -function openAccordionByHash() { - var anchor = window.location.hash; - function expandElement() { - if ($(anchor).parents('.expand').length > 0) { - return $(anchor).closest('.expand').children('.expand-label'); - } else if ($(anchor).hasClass('expand')){ - return $(anchor).children('.expand-label'); - } - }; - - if (expandElement() != null) { - if (expandElement().children('.expand-toggle').hasClass('open')) {} - else { - expandElement().children('.expand-toggle').trigger('click'); - }; - }; -}; // Open accordions by hash on page load. openAccordionByHash() @@ -152,3 +138,9 @@ $('.article--content a').each(function() { $(this).attr('target', '_blank'); }; }) + +} + +export default function ContentInteractions() { + contentInteractions(); +} diff --git a/assets/js/cookies.js b/assets/js/cookies.js index 4567941834..84969fb159 100644 --- a/assets/js/cookies.js +++ b/assets/js/cookies.js @@ -13,22 +13,11 @@ - callouts: Feature callouts that have been seen (array) - influxdata_docs_ported: Temporary cookie to help port old cookies to new structure */ +import Cookies from 'js-cookie'; // Prefix for all InfluxData docs cookies const cookiePrefix = 'influxdata_docs_'; -/* - Initialize a cookie with a default value. -*/ -initializeCookie = (cookieName, defaultValue) => { - fullCookieName = cookiePrefix + cookieName; - - // Check if the cookie exists before initializing the cookie - if (Cookies.get(fullCookieName) === undefined) { - Cookies.set(fullCookieName, defaultValue); - } -}; - // Initialize all InfluxData docs cookies with defaults /* @@ -49,11 +38,22 @@ var defaultPref = { v3_wayfinding_show: true, }; +/* + Initialize a cookie with a default value. +*/ +function initializeCookie (cookieName, defaultValue) { + const fullCookieName = cookiePrefix + cookieName; + if (typeof defaultValue !== 'string') { + val = JSON.stringify(defaultValue); + Cookies.set(fullCookieName, val); + } +}; + /* Retrieve a preference from the preference cookie. If the cookie doesn't exist, initialize it with default values. */ -getPreference = prefName => { +const getPreference = prefName => { // Initialize the preference cookie if it doesn't already exist if (Cookies.get(prefCookieName) === undefined) { initializeCookie('preferences', defaultPref); @@ -68,17 +68,17 @@ getPreference = prefName => { }; // Set a preference in the preferences cookie -setPreference = (prefID, prefValue) => { +const setPreference = (prefID, prefValue) => { var prefString = Cookies.get(prefCookieName); let prefObj = JSON.parse(prefString); - + prefObj[prefID] = prefValue; - Cookies.set(prefCookieName, prefObj); + Cookies.set(prefCookieName, JSON.stringify(prefObj)); }; // Return an object containing all preferences -getPreferences = () => JSON.parse(Cookies.get(prefCookieName)); +const getPreferences = () => JSON.parse(Cookies.get(prefCookieName)); /* //////////////////////////////////////////////////////////////////////////////// @@ -113,7 +113,7 @@ var defaultUrlsCookie = { }; // Return an object that contains all InfluxDB urls stored in the urls cookie -getInfluxDBUrls = () => { +const getInfluxDBUrls = () => { // Initialize the urls cookie if it doesn't already exist if (Cookies.get(urlCookieName) === undefined) { initializeCookie('urls', defaultUrlsCookie); @@ -123,7 +123,7 @@ getInfluxDBUrls = () => { }; // Get the current or previous URL for a specific product or a custom url -getInfluxDBUrl = product => { +const getInfluxDBUrl = product => { // Initialize the urls cookie if it doesn't already exist if (Cookies.get(urlCookieName) === undefined) { initializeCookie('urls', defaultUrlsCookie); @@ -142,23 +142,23 @@ getInfluxDBUrl = product => { Input should be an object where the key is the product and the value is the URL to set for that product. */ -setInfluxDBUrls = updatedUrlsObj => { +const setInfluxDBUrls = updatedUrlsObj => { var urlsString = Cookies.get(urlCookieName); let urlsObj = JSON.parse(urlsString); - newUrlsObj = { ...urlsObj, ...updatedUrlsObj }; + const newUrlsObj = { ...urlsObj, ...updatedUrlsObj }; - Cookies.set(urlCookieName, newUrlsObj); + Cookies.set(urlCookieName, JSON.stringify(newUrlsObj)); }; // Set an InfluxDB URL to an empty string in the urls cookie -removeInfluxDBUrl = product => { +const removeInfluxDBUrl = product => { var urlsString = Cookies.get(urlCookieName); - let urlsObj = JSON.parse(urlsString); + const urlsObj = JSON.parse(urlsString); urlsObj[product] = ''; - Cookies.set(urlCookieName, urlsObj); + Cookies.set(urlCookieName, JSON.stringify(urlsObj)); }; /* @@ -175,7 +175,7 @@ var defaultNotifications = { callouts: [], }; -getNotifications = () => { +const getNotifications = () => { // Initialize the notifications cookie if it doesn't already exist if (Cookies.get(notificationCookieName) === undefined) { initializeCookie('notifications', defaultNotifications); @@ -199,11 +199,9 @@ getNotifications = () => { If the notification ID exists in the array assigned to the specified type, the notification has been read. */ -notificationIsRead = (notificationID, notificationType) => { +const notificationIsRead = (notificationID, notificationType) => { let notificationsObj = getNotifications(); - readNotifications = notificationsObj[`${notificationType}s`]; - - return readNotifications.includes(notificationID); + return notificationsObj[`${notificationType}s`].includes(notificationID); }; /* @@ -215,12 +213,26 @@ notificationIsRead = (notificationID, notificationType) => { The notification ID is added to the array assigned to the specified type. */ -setNotificationAsRead = (notificationID, notificationType) => { +const setNotificationAsRead = (notificationID, notificationType) => { let notificationsObj = getNotifications(); let readNotifications = notificationsObj[`${notificationType}s`]; readNotifications.push(notificationID); notificationsObj[notificationType + 's'] = readNotifications; - Cookies.set(notificationCookieName, notificationsObj); + Cookies.set(notificationCookieName, JSON.stringify(notificationsObj)); }; + +export { + initializeCookie, + getPreference, + setPreference, + getPreferences, + getInfluxDBUrls, + getInfluxDBUrl, + setInfluxDBUrls, + removeInfluxDBUrl, + getNotifications, + notificationIsRead, + setNotificationAsRead, +} diff --git a/assets/js/custom-timestamps.js b/assets/js/custom-timestamps.js index 2442931f8e..c1c4004053 100644 --- a/assets/js/custom-timestamps.js +++ b/assets/js/custom-timestamps.js @@ -1,3 +1,8 @@ +import $ from 'jquery'; +import { setPreference, getPreference } from './cookies.js'; +import { toggleModal } from './modal.js'; +import { Datepicker } from 'vanillajs-datepicker'; + // Placeholder start date used in InfluxDB custom timestamps const defaultStartDate = '2022-01-01'; diff --git a/assets/js/datetime.js b/assets/js/datetime.js index ec0f8ee2b7..7e88c1a858 100644 --- a/assets/js/datetime.js +++ b/assets/js/datetime.js @@ -1,3 +1,5 @@ +import $ from 'jquery'; + const monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; var date = new Date() var currentTimestamp = date.toISOString().replace(/^(.*)(\.\d+)(Z)/, '$1$3') // 2023-01-01T12:34:56Z diff --git a/assets/js/docs-themes.js b/assets/js/docs-themes.js index ce9fa90f41..2c19ff0f46 100644 --- a/assets/js/docs-themes.js +++ b/assets/js/docs-themes.js @@ -3,6 +3,8 @@ http://www.thesitewizard.com/javascripts/change-style-sheets.shtml */ +import { getPreference, setPreference } from './cookies.js'; + // *** TO BE CUSTOMISED *** var style_preference_name = 'theme'; var style_cookie_duration = 30; @@ -36,7 +38,15 @@ function switchStyle (css_title) { function setStyleFromCookie () { var css_title = `${getPreference(style_preference_name)}-theme`; - if (css_title !== undefined) { + if (css_title !== 'undefined-theme') { switchStyle(css_title); } } + +export { + setStyleFromCookie, + switchStyle, + style_preference_name, + style_cookie_duration, + style_domain +} \ No newline at end of file diff --git a/assets/js/feature-callouts.js b/assets/js/feature-callouts.js index 918d783c22..6e68e6e540 100644 --- a/assets/js/feature-callouts.js +++ b/assets/js/feature-callouts.js @@ -5,6 +5,7 @@ Callouts are treated as notifications and use the notification cookie API in assets/js/cookies.js. */ +import $ from 'jquery'; // Get notification ID function getCalloutID (el) { diff --git a/assets/js/flux-group-keys.js b/assets/js/flux-group-keys.js index 80ab46b70f..87cf09c374 100644 --- a/assets/js/flux-group-keys.js +++ b/assets/js/flux-group-keys.js @@ -1,3 +1,5 @@ +import $ from 'jquery'; + var tablesElement = $("#flux-group-keys-demo #grouped-tables") // Sample data @@ -153,5 +155,10 @@ $(".column-list label").click(function () { buildGroupExample(); }); -// Group and render tables on load -groupData() +export { + buildTable, + buildTables, + groupData, + getChecked, + toggleCheckbox +}; diff --git a/assets/js/flux-influxdb-versions.js b/assets/js/flux-influxdb-versions.js index 4e6f66afeb..42e19e8e4d 100644 --- a/assets/js/flux-influxdb-versions.js +++ b/assets/js/flux-influxdb-versions.js @@ -1,6 +1,7 @@ /* Interactions related to the Flux/InfluxDB version modal */ +import $ from 'jquery'; const fluxInfluxDBModal = '.modal-content#flux-influxdb-versions' const pageType = ($(document).attr('title')).includes("package") ? "package" : "function"; diff --git a/assets/js/home-interactions.js b/assets/js/home-interactions.js index a90df14cd4..1bfc8db62d 100644 --- a/assets/js/home-interactions.js +++ b/assets/js/home-interactions.js @@ -1,3 +1,5 @@ +import $ from 'jquery'; + $('.exp-btn').click(function() { var targetBtnElement = $(this).parent() $('.exp-btn > p', targetBtnElement).fadeOut(100); diff --git a/assets/js/influxdb-url.js b/assets/js/influxdb-url.js index a2f1527487..d5a26afde7 100644 --- a/assets/js/influxdb-url.js +++ b/assets/js/influxdb-url.js @@ -1,3 +1,8 @@ +import $ from 'jquery'; +import { getInfluxDBUrls, getInfluxDBUrl, getPreference, setInfluxDBUrls, setPreference } from './cookies.js'; +// Import parameters passed from the calling page to js.Build. +import { cloudUrls } from '@params'; + var placeholderUrls = { oss: 'http://localhost:8086', cloud: 'https://cloud2.influxdata.com', @@ -646,8 +651,8 @@ productsWithUniqueURLs.forEach(function (productEl) { //////////////////////////////////////////////////////////////////////////////// // Extract the protocol and hostname of referrer -referrerMatch = document.referrer.match(/^(?:[^\/]*\/){2}[^\/]+/g); -referrerHost = referrerMatch ? referrerMatch[0] : ''; +const referrerMatch = document.referrer.match(/^(?:[^\/]*\/){2}[^\/]+/g); +const referrerHost = referrerMatch ? referrerMatch[0] : ''; // Check if the referrerHost is one of the cloud URLs // cloudUrls is built dynamically in layouts/partials/footer/javascript.html @@ -668,3 +673,5 @@ if (getUrls().dedicated == 'cluster-id.influxdb.io') { storeUrl('dedicated', 'cluster-id.a.influxdb.io', getUrls().dedicated); updateUrls(getPrevUrls(), getUrls()); } + +export { getUrls, referrerHost }; \ No newline at end of file diff --git a/assets/js/js.cookie.js b/assets/js/js.cookie.js deleted file mode 100644 index 9a0945ed89..0000000000 --- a/assets/js/js.cookie.js +++ /dev/null @@ -1,165 +0,0 @@ -/*! - * JavaScript Cookie v2.2.0 - * https://github.com/js-cookie/js-cookie - * - * Copyright 2006, 2015 Klaus Hartl & Fagner Brack - * Released under the MIT license - */ -;(function (factory) { - var registeredInModuleLoader = false; - if (typeof define === 'function' && define.amd) { - define(factory); - registeredInModuleLoader = true; - } - if (typeof exports === 'object') { - module.exports = factory(); - registeredInModuleLoader = true; - } - if (!registeredInModuleLoader) { - var OldCookies = window.Cookies; - var api = window.Cookies = factory(); - api.noConflict = function () { - window.Cookies = OldCookies; - return api; - }; - } -}(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 * 864e+5); - 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) {} - - 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 (!this.json && 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) {} - } - - if (key === name) { - result = cookie; - break; - } - - if (!key) { - result[name] = cookie; - } - } catch (e) {} - } - - 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 () {}); -})); diff --git a/assets/js/keybindings.js b/assets/js/keybindings.js index 6c8f2fcbe2..1bd4529992 100644 --- a/assets/js/keybindings.js +++ b/assets/js/keybindings.js @@ -1,3 +1,5 @@ +import $ from 'jquery'; + // Dynamically update keybindings or hotkeys function getPlatform() { if (/Mac/.test(navigator.platform)) { diff --git a/assets/js/list-filters.js b/assets/js/list-filters.js index 7b008dcb68..3e95616aa5 100644 --- a/assets/js/list-filters.js +++ b/assets/js/list-filters.js @@ -1,3 +1,5 @@ +import $ from 'jquery'; + // Count tag elements function countTag(tag) { return $(".visible[data-tags*='" + tag + "']").length diff --git a/assets/js/main.js b/assets/js/main.js index 1833629e02..7920404c7f 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -1,4 +1,80 @@ // assets/js/main.js -import { apiDocComponent } from "./api-doc"; +/** Import existing JS as "libraries" for now + * until we can refactor them into component modules. + */ +import * as codeblocksPreferences from './api-libs.js' +import * as codeControls from './code-controls.js'; +import * as cookies from './cookies.js'; +import * as datetime from './datetime.js'; +import * as featureCallouts from './feature-callouts.js'; +import * as fluxGroupKeys from './flux-group-keys.js'; +import * as fluxInfluxDBVersions from './flux-influxdb-versions.js'; +import * as homeInteractions from './home-interactions.js'; +import * as influxDBUrls from './influxdb-url.js'; +import * as keybindings from './keybindings.js'; +import * as listFilters from './list-filters.js'; +import * as modals from './modals.js'; +import * as notifications from './notifications.js'; +import * as pageFeedback from './page-feedback.js'; +import * as releaseTOC from './release-toc.js'; +import * as scroll from './scroll.js'; +import * as searchInteractions from './search-interactions.js'; +import * as sidebarToggle from './sidebar-toggle.js'; +import * as tabbedContent from './tabbed-content.js'; +import * as themes from './docs-themes.js'; +import * as v3wayfinding from './v3-wayfinding.js'; -export { apiDocComponent }; \ No newline at end of file + +/** UI Component-like Modules **/ +/** Following the React JSX component pattern, a component is a module + * that exports a single function + * encapsulating the behavior of the component. + * This function should + * be called in a DOMContentLoaded event listener to ensure that the + * component is properly initialized. +*/ +import ApiReferencePage from "./api-doc/ApiReferencePage.js"; +import ContentInteractions from './content-interactions.js'; +import FluxGroupKeys from './FluxGroupKeys.js'; +import SearchInput from './search-interactions.js'; +import Sidebar from './Sidebar.js' +import ThemeStyle from './ThemeStyle.js'; +import VersionSelector from './version-selector.js'; + +// Import parameters passed from the calling page to js.Build. +import * as pageParams from '@params'; + +document.addEventListener('DOMContentLoaded', () => { + // Expose libraries and components within a namespaced object. + window.influxdatadocs = { + ApiReferencePage, + codeblocksPreferences, + codeControls, + ContentInteractions, + cookies, + datetime, + featureCallouts, + fluxGroupKeys, + FluxGroupKeys, + fluxInfluxDBVersions, + homeInteractions, + influxDBUrls, + keybindings, + listFilters, + modals, + notifications, + pageFeedback, + pageParams, + releaseTOC, + scroll, + searchInteractions, + SearchInput, + Sidebar, + sidebarToggle, + tabbedContent, + themes, + ThemeStyle, + v3wayfinding, + VersionSelector, + }; +}); \ No newline at end of file diff --git a/assets/js/modals.js b/assets/js/modals.js index 9111f1945e..c4352ae2b7 100644 --- a/assets/js/modals.js +++ b/assets/js/modals.js @@ -1,6 +1,7 @@ //////////////////////////////////////////////////////////////////////////////// /////////////////////// General modal window interactions ////////////////////// //////////////////////////////////////////////////////////////////////////////// +import $ from 'jquery'; // Toggle the URL selector modal window function toggleModal(modalID="") { diff --git a/assets/js/notifications.js b/assets/js/notifications.js index 2400f90529..8f5b16f15e 100644 --- a/assets/js/notifications.js +++ b/assets/js/notifications.js @@ -4,6 +4,7 @@ messages array in the influxdata_docs_notifications cookie. IDs in the messages array are considered read and no longer appear to the user. */ +import $ from 'jquery'; // Get notification ID function notificationID(el) { diff --git a/assets/js/page-feedback.js b/assets/js/page-feedback.js index af2ea0cd29..8ec5e665fa 100644 --- a/assets/js/page-feedback.js +++ b/assets/js/page-feedback.js @@ -2,6 +2,7 @@ * This file controls the interactions and life-cycles of the page feedback * buttons and modal. */ +import $ from 'jquery'; // Collect data from the page path const pathArr = location.pathname.split('/').slice(1, -1) diff --git a/assets/js/release-toc.js b/assets/js/release-toc.js index 42858fccc7..eb51b62ae0 100644 --- a/assets/js/release-toc.js +++ b/assets/js/release-toc.js @@ -4,6 +4,7 @@ * This script is used to generate a table of contents for the * release notes pages. */ +import $ from 'jquery'; // Use jQuery filter to get an array of all the *release* h2 elements const releases = $('h2').filter( diff --git a/assets/js/scroll.js b/assets/js/scroll.js new file mode 100644 index 0000000000..3c7af0d723 --- /dev/null +++ b/assets/js/scroll.js @@ -0,0 +1,21 @@ +import $ from 'jquery'; + +export function scrollToAnchor(target) { + var $target = $(target); + if($target && $target.length > 0) { + $('html, body').stop().animate({ + 'scrollTop': ($target.offset().top) + }, 400, 'swing', function () { + window.location.hash = target; + }); + + // Unique accordion functionality + // If the target is an accordion element, open the accordion after scrolling + if ($target.hasClass('expand')) { + if ($(target + ' .expand-label .expand-toggle').hasClass('open')) {} + else { + $(target + '> .expand-label').trigger('click'); + }; + }; + } +} \ No newline at end of file diff --git a/assets/js/search-interactions.js b/assets/js/search-interactions.js index 4f8fdd8ac1..8b3a12182f 100644 --- a/assets/js/search-interactions.js +++ b/assets/js/search-interactions.js @@ -1,10 +1,14 @@ -// Fade content wrapper when focusing on search input -$('#algolia-search-input').focus(function() { - $('.content-wrapper').fadeTo(300, .35); -}) +import $ from 'jquery'; -// Hide search dropdown when leaving search input -$('#algolia-search-input').blur(function() { - $('.content-wrapper').fadeTo(200, 1); - $('.ds-dropdown-menu').hide(); -}) +export default function SearchInput() { + // Fade content wrapper when focusing on search input + $('#algolia-search-input').focus(function() { + $('.content-wrapper').fadeTo(300, .35); + }) + + // Hide search dropdown when leaving search input + $('#algolia-search-input').blur(function() { + $('.content-wrapper').fadeTo(200, 1); + $('.ds-dropdown-menu').hide(); + }) +}; \ No newline at end of file diff --git a/assets/js/sidebar-toggle.js b/assets/js/sidebar-toggle.js index 2c20b3ddf5..d094c64db4 100644 --- a/assets/js/sidebar-toggle.js +++ b/assets/js/sidebar-toggle.js @@ -3,6 +3,8 @@ http://www.thesitewizard.com/javascripts/change-style-sheets.shtml */ +import { getPreference, setPreference } from './cookies.js'; + // *** TO BE CUSTOMISED *** var sidebar_state_preference_name = 'sidebar_state'; var sidebar_state_duration = 30; @@ -43,3 +45,11 @@ function setSidebarState () { toggleSidebar(toggle_state); } } + +export { + setSidebarState, + toggleSidebar, + sidebar_state_preference_name, + sidebar_state_duration, + style_domain +} diff --git a/assets/js/tabbed-content.js b/assets/js/tabbed-content.js index 536301828b..b6244defcc 100644 --- a/assets/js/tabbed-content.js +++ b/assets/js/tabbed-content.js @@ -1,5 +1,8 @@ //////////////////////////////// Tabbed Content //////////////////////////////// +import $ from 'jquery'; +import { scrollToAnchor } from './scroll.js'; + /** * NOTE: Tab elements are whitelisted elements that do not trigger * smoothscroll when clicked. The whitelist is defined in content-interactions.js. @@ -30,9 +33,6 @@ function tabbedContent (container, tab, content) { }); } -tabbedContent('.code-tabs-wrapper', '.code-tabs p a', '.code-tab-content'); -tabbedContent('.tabs-wrapper', '.tabs p a', '.tab-content'); - function getTabQueryParam () { const queryParams = new URLSearchParams(window.location.search); return $('