| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| function _interopDefault(ex) { |
| return ex && typeof ex === "object" && "default" in ex ? ex["default"] : ex; |
| } |
| import { |
| createContext, |
| createElement, |
| useContext, |
| Component, |
| useMemo, |
| useRef, |
| Fragment, |
| SFC, |
| useState, |
| useEffect |
| } from "react"; |
| import * as React from "react"; |
| |
| import { |
| useMenus, |
| useWindowSize, |
| usePrevious, |
| doczState |
| } from "../../../../docz-lib/docz/dist"; |
| import styled from "styled-components"; |
| import _unionBy from "lodash/fp/unionBy"; |
| import _get from "lodash/fp/get"; |
| import { Logo } from "../Logo"; |
| import { Search } from "../Search"; |
| import { Menu } from "./Menu"; |
| import { Docz } from "./Docz"; |
| import { Hamburger } from "./Hamburger"; |
| import { MenuLink } from "./MenuLink"; |
| import { SubMenu } from "./SubMenu"; |
| import { get } from "../../../utils/theme"; |
| import { mq, breakpoints } from "../../../styles/responsive"; |
| import Utils from "../../../utils/utils"; |
| |
| const _pipe = _interopDefault(require("lodash/fp/pipe")); |
| const _omit = _interopDefault(require("lodash/fp/omit")); |
| const sort = _interopDefault(require("array-sort")); |
| const _flattenDepth = _interopDefault(require("lodash/fp/flattenDepth")); |
| const match = _interopDefault(require("match-sorter")); |
| const ulid = require("ulid"); |
| const WrapperProps = { |
| opened: true, |
| theme: "" |
| }; |
| |
| const sidebarBg = get("colors.sidebarBg"); |
| const sidebarText = get("colors.sidebarText"); |
| const sidebarBorder = get("colors.sidebarBorder"); |
| const Wrapper = styled.div` |
| position: relative; |
| width: 320px; |
| min-width: 320px; |
| min-height: 100vh; |
| background: ${sidebarBg}; |
| transition: transform 0.2s, background 0.3s; |
| z-index: 1000; |
| |
| ${mq({ |
| position: ["absolute", "absolute", "absolute", "relative"] |
| })}; |
| |
| dl { |
| padding: 0; |
| margin: 0 10px; |
| } |
| |
| dl a { |
| font-weight: 400; |
| } |
| |
| @media screen and (max-width: ${breakpoints.desktop - 1}px) { |
| transform: translateX(${p => (p.opened ? "-100%" : "0")}); |
| position: ${p => (p.opened ? "auto" : "fixed")}; |
| } |
| |
| ${get("styles.sidebar")}; |
| `; |
| |
| Wrapper.defaultProps = WrapperProps; |
| const GitHubDivContainer = styled.div` |
| position: relative; |
| `; |
| const GitHubDivContent = styled.div` |
| position: fixed; |
| top: 0px; |
| right: 10px; |
| `; |
| |
| const Content = styled.div` |
| position: fixed; |
| top: 0; |
| left: 0; |
| display: flex; |
| flex-direction: column; |
| width: 280px; |
| min-width: 320px; |
| height: 100%; |
| max-height: 100vh; |
| background: ${sidebarBg}; |
| `; |
| |
| const Menus = styled.nav` |
| flex: 1; |
| overflow-y: auto; |
| margin-bottom: 10px; |
| `; |
| |
| const Empty = styled.div` |
| flex: 1; |
| opacity: 0.7; |
| padding: 0 24px; |
| color: ${sidebarText}; |
| `; |
| |
| const Footer = styled.div` |
| padding: 10px 0; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| font-size: 14px; |
| color: ${sidebarText}; |
| border-top: 1px dashed ${sidebarBorder}; |
| `; |
| |
| const FooterLink = styled.a` |
| padding: 0; |
| margin: 0; |
| margin-left: 5px; |
| `; |
| |
| const FooterLogo = styled(Docz)` |
| fill: ${sidebarText}; |
| `; |
| |
| const OpenProps = { |
| opened: false |
| }; |
| const ToggleBackground = styled.div` |
| content: ""; |
| display: ${p => (p.opened ? "none" : "block")}; |
| position: fixed; |
| background-color: rgba(0, 0, 0, 0.4); |
| width: 100vw; |
| height: 100vh; |
| top: 0; |
| bottom: 0; |
| left: 0; |
| right: 0; |
| cursor: pointer; |
| z-index: 99; |
| `; |
| const UNKNOWN_POS = Infinity; |
| function compare(a, b, reverse) { |
| if (a < b) return reverse ? 1 : -1; |
| if (a > b) return reverse ? -1 : 1; |
| return 0; |
| } |
| const sortByName = (a, b) => { |
| return a.name < b.name ? -1 : a.name > b.name ? 1 : 0; |
| }; |
| const findPos = (item, orderedList = []) => { |
| const name = typeof item !== "string" ? _get("name", item) : item; |
| const pos = orderedList.findIndex(item => item === name); |
| return pos !== -1 ? pos : UNKNOWN_POS; |
| }; |
| const compareWithMenu = (to = []) => (a, b) => { |
| const list = to.map(i => i.name || i); |
| return compare(findPos(a, list), findPos(b, list)); |
| }; |
| const noMenu = entry => !entry.submenu; |
| const parseItemStr = item => |
| typeof item === "string" |
| ? { |
| name: item |
| } |
| : item; |
| const normalize = item => { |
| const selected = parseItemStr(item); |
| return Object.assign({}, selected, { |
| id: selected.id || ulid.ulid(), |
| parent: _get("parent", selected) || _get("parent", item), |
| menu: Array.isArray(selected.menu) |
| ? selected.menu.map(normalize) |
| : selected.menu |
| }); |
| }; |
| |
| const clean = item => (item.href || item.route ? _omit("menu", item) : item); |
| const normalizeAndClean = _pipe(normalize, clean); |
| const fromMenu = submenu => entry => entry.submenu === submenu; |
| |
| const entryAsMenu = entry => ({ |
| name: entry.name, |
| route: entry.route, |
| parent: entry.parent, |
| submenu: entry.submenu, |
| menu: entry.menu |
| }); |
| |
| function flatArrFromObject(arr, prop, menus) { |
| const reducer = (arr, obj) => { |
| const value = _get(prop)(obj); |
| |
| return value ? arr.concat([value]) : arr; |
| }; |
| |
| return Array.from(new Set(arr.reduce(reducer, []))); |
| } |
| const entriesOfSubMenu = (submenu, entries) => |
| entries.filter(fromMenu(submenu)).map(entryAsMenu); |
| const entriesOfMenu = (menu, entries) => |
| entries.filter(fromMenu(menu)).map(entryAsMenu); |
| const parseMenu = entries => name => ({ |
| name, |
| menu: entriesOfMenu(name, entries) |
| }); |
| |
| // const parseSubMenu = entries => name => ({ |
| // name, |
| // submenu: entriesOfSubMenu(name, entries), |
| // menu:entries.menu |
| // }); |
| |
| const parseSubMenu = function parseSubMenu(entries) { |
| return function(name) { |
| return { |
| name: name, |
| submenu: entriesOfSubMenu(name, entries), |
| menu: entriesOfMenu(name, entries) |
| }; |
| }; |
| }; |
| const menusFromEntries = entries => { |
| const entriesWithoutMenu = entries.filter(noMenu).map(entryAsMenu); |
| const menus = flatArrFromObject(entries, "menu").map(parseMenu(entries)); |
| const submenus = flatArrFromObject(entries, "submenu", menus).map( |
| parseSubMenu(entries) |
| ); |
| |
| for (var x in menus) { |
| for (var without of entriesWithoutMenu) { |
| if (without.name == menus[x].name) { |
| menus[x] = without; |
| break; |
| } |
| } |
| } |
| for (var x in menus) { |
| for (var y in submenus) { |
| if (menus[x].name == submenus[y].menu[0].menu) { |
| if (menus[x].menu.length > 0) { |
| menus[x].menu.push(submenus[y]); |
| } else { |
| menus[x].menu.push(submenus[y]); |
| } |
| } |
| } |
| } |
| return _unionBy("name", menus, entriesWithoutMenu); |
| }; |
| const mergeMenus = (entriesMenu, configMenu) => { |
| const first = entriesMenu.map(normalizeAndClean); |
| const second = configMenu.map(normalizeAndClean); |
| |
| const merged = _unionBy("name", first, second); |
| return merged.map(item => { |
| if (!item.menu) return item; |
| const found = second.find(i => i.name === item.name); |
| const foundMenu = found && found.menu; |
| return Object.assign({}, item, { |
| menu: foundMenu |
| ? mergeMenus(item.menu, foundMenu) |
| : item.menu || found.menu |
| }); |
| }); |
| }; |
| const sortMenus = (first, second = []) => { |
| const sorted = sort(first, compareWithMenu(second), sortByName); |
| return sorted.map(item => { |
| if (!item.menu) return item; |
| const found = second.find(menu => menu.name === item.name); |
| const foundMenu = found && found.menu; |
| return Object.assign({}, item, { |
| menu: foundMenu ? sortMenus(item.menu, foundMenu) : item.menu |
| }); |
| }); |
| }; |
| const search = (val, menu) => { |
| const items = menu.map(item => [item].concat(item.menu || [])); |
| |
| const flattened = _flattenDepth(2, items); |
| |
| const flattenedDeduplicated = [...new Set(flattened)]; |
| const matchedValues = match(flattenedDeduplicated, val, { |
| keys: ["name"] |
| }); |
| return matchedValues; |
| }; |
| |
| const filterMenus = (items, filter) => { |
| if (!filter) return items; |
| return items.filter(filter).map(item => { |
| if (!item.menu) return item; |
| return Object.assign({}, item, { |
| menu: item.menu.filter(filter) |
| }); |
| }); |
| }; |
| const useMenusCustom = opts => { |
| const { query = "" } = opts || {}; |
| const { entries, config } = useContext(doczState.context); |
| if (!entries) return null; |
| const arr = entries.map(({ value }) => value); |
| |
| const entriesMenu = menusFromEntries(arr); |
| |
| const sorted = React.useMemo(() => { |
| const merged = mergeMenus(entriesMenu, config.menu); |
| const result = sortMenus(merged, config.menu); |
| return filterMenus(result, opts && opts.filter); |
| }, [entries, config]); |
| return query && query.length > 0 ? search(query, sorted) : sorted; |
| // return entriesMenu; |
| }; |
| |
| ToggleBackground.defaultProps = OpenProps; |
| const getActiveMenu = () => { |
| const { localStorageKeys } = Utils; |
| let act_menu = JSON.parse(localStorage.getItem(localStorageKeys.ACTIVEMENU)); |
| if (!act_menu) { |
| act_menu = ["Documentation"]; |
| localStorage.setItem(localStorageKeys.ACTIVEMENU, JSON.stringify(act_menu)); |
| } |
| return act_menu; |
| }; |
| export const Sidebar = () => { |
| const [activeMenu, setActiveMenu] = useState(getActiveMenu()); |
| const [hidden, setHidden] = useState(true); |
| const [query, setQuery] = useState(""); |
| const menus = useMenusCustom({ query }); |
| const windowSize = useWindowSize(); |
| const isDesktop = windowSize.innerWidth >= breakpoints.desktop; |
| const prevIsDesktop = usePrevious(isDesktop); |
| const navRef = useRef(); |
| |
| useEffect(() => { |
| if (!hidden && !prevIsDesktop && isDesktop) { |
| setHidden(true); |
| document.documentElement.classList.remove("with-overlay"); |
| } |
| }); |
| |
| useEffect(() => { |
| const { localStorageKeys } = Utils; |
| const navTop = parseInt(localStorage.getItem(localStorageKeys.NAVPOSITION)); |
| if (navTop) { |
| navRef.current.scrollTop = navTop; |
| } |
| }, []); |
| |
| const addOverlayClass = isHidden => { |
| const method = !isHidden ? "add" : "remove"; |
| |
| if (typeof window !== "undefined" && !isDesktop) { |
| document.documentElement.classList[method]("with-overlay"); |
| } |
| }; |
| |
| const handleSidebarToggle = () => { |
| if (isDesktop) return; |
| setHidden(s => !s); |
| addOverlayClass(!hidden); |
| }; |
| const handleScroll = () => { |
| const { localStorageKeys } = Utils; |
| localStorage.setItem( |
| localStorageKeys.NAVPOSITION, |
| navRef.current.scrollTop |
| ); |
| }; |
| const handleActiveMenu = menu => { |
| const { localStorageKeys } = Utils; |
| const t_activeMenu = JSON.parse(JSON.stringify(activeMenu)); |
| const index = t_activeMenu.findIndex(a => a === menu.name); |
| if (index === -1) { |
| t_activeMenu.push(menu.name); |
| setActiveMenu(t_activeMenu); |
| } else { |
| t_activeMenu.splice(index, 1); |
| setActiveMenu([...t_activeMenu]); |
| } |
| localStorage.setItem( |
| localStorageKeys.ACTIVEMENU, |
| JSON.stringify(t_activeMenu) |
| ); |
| }; |
| let outputHtml = ( |
| <Fragment> |
| <Wrapper opened={hidden}> |
| <Content> |
| <Hamburger opened={!hidden} onClick={handleSidebarToggle} /> |
| <Logo showBg={true} /> |
| |
| {menus && menus.length === 0 ? ( |
| <Empty>No documents founda.</Empty> |
| ) : ( |
| <Menus ref={navRef} onScroll={handleScroll}> |
| {menus && |
| menus.map(menu => ( |
| <Menu |
| key={menu.id} |
| item={menu} |
| sidebarToggle={handleSidebarToggle} |
| activeMenu={activeMenu} |
| handleActiveMenu={handleActiveMenu} |
| /> |
| ))} |
| </Menus> |
| )} |
| {/* <Footer> |
| Built with |
| <FooterLink href="https://docz.site" target="_blank"> |
| <FooterLogo width={40} /> |
| </FooterLink> |
| </Footer>*/} |
| </Content> |
| </Wrapper> |
| <ToggleBackground opened={hidden} onClick={handleSidebarToggle} /> |
| </Fragment> |
| ); |
| if (query.length > 0) { |
| outputHtml = ( |
| <Fragment> |
| <Wrapper> |
| <Content> |
| <Hamburger opened={!hidden} onClick={handleSidebarToggle} /> |
| <Logo showBg={!hidden} /> |
| |
| <MenuLink item={menus}></MenuLink> |
| <Menus ref={navRef} onScroll={handleScroll}> |
| {menus && |
| menus.map(menu => ( |
| <SubMenu |
| key={menu.id} |
| item={menu} |
| sidebarToggle={handleSidebarToggle} |
| collapseAll={Boolean(query.length)} |
| /> |
| ))} |
| </Menus> |
| </Content> |
| </Wrapper> |
| </Fragment> |
| ); |
| } |
| return outputHtml; |
| }; |