blob: 93c8743a43e5f0faf564aa377223d81c84198590 [file] [log] [blame]
/**
* 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;
};