From 07e43676665bdc009e135b767e6d43c536b413fa Mon Sep 17 00:00:00 2001 From: Marcin Aman Date: Tue, 10 Nov 2020 16:54:57 +0100 Subject: New breadcrumbs (#1590) Add top navbar --- .../frontend/src/main/components/app/index.scss | 6 -- .../main/components/pageSummary/pageSummary.scss | 1 + .../main/components/pageSummary/pageSummary.tsx | 24 +++-- plugins/base/frontend/src/main/components/root.tsx | 70 +++++--------- .../src/main/components/search/search.scss | 1 + .../src/main/kotlin/renderers/html/HtmlRenderer.kt | 37 ++++---- .../kotlin/renderers/html/htmlPreprocessors.kt | 2 +- .../dokka/scripts/platform-content-handler.js | 28 +++++- .../base/src/main/resources/dokka/styles/style.css | 103 +++++++++++++++------ 9 files changed, 165 insertions(+), 107 deletions(-) (limited to 'plugins/base') diff --git a/plugins/base/frontend/src/main/components/app/index.scss b/plugins/base/frontend/src/main/components/app/index.scss index 601ddfa7..97f91292 100644 --- a/plugins/base/frontend/src/main/components/app/index.scss +++ b/plugins/base/frontend/src/main/components/app/index.scss @@ -17,17 +17,11 @@ html, } .search-content { - padding-top: 24px; - margin: 0 41px; - position: absolute; - top: 0; - right: 0; z-index: 8; background-color: #f4f4f4; } @media screen and (max-width: 759px){ .search-content { - margin: 0 8px; } } diff --git a/plugins/base/frontend/src/main/components/pageSummary/pageSummary.scss b/plugins/base/frontend/src/main/components/pageSummary/pageSummary.scss index 400631e6..aaa897a8 100644 --- a/plugins/base/frontend/src/main/components/pageSummary/pageSummary.scss +++ b/plugins/base/frontend/src/main/components/pageSummary/pageSummary.scss @@ -52,6 +52,7 @@ &>a { margin: 0 2em; + cursor: pointer; } &.selected { diff --git a/plugins/base/frontend/src/main/components/pageSummary/pageSummary.tsx b/plugins/base/frontend/src/main/components/pageSummary/pageSummary.tsx index 1aa65d9b..c5a8344f 100644 --- a/plugins/base/frontend/src/main/components/pageSummary/pageSummary.tsx +++ b/plugins/base/frontend/src/main/components/pageSummary/pageSummary.tsx @@ -5,6 +5,8 @@ import Scrollspy from 'react-scrollspy' type PageSummaryProps = { entries: PageSummaryEntry[], + containerId: string, //Id of a container that has scroll enabled + offsetComponentId: string, //Id of a top navbar component } type PageSummaryEntry = { @@ -15,13 +17,12 @@ type PageSummaryEntry = { type SourceSetFilterKey = string -export const PageSummary: React.FC = ({ entries }: PageSummaryProps) => { +const getElementHeightFromDom = (elementId: string): number => document.getElementById(elementId).offsetHeight + +export const PageSummary: React.FC = ({ entries, containerId, offsetComponentId }: PageSummaryProps) => { const [hidden, setHidden] = useState(true); const [displayableEntries, setDisplayableEntries] = useState(entries) - - const handleMouseHover = () => { - setHidden(!hidden) - } + const topOffset = getElementHeightFromDom(offsetComponentId) useEffect(() => { const handeEvent = (event: CustomEvent) => { @@ -33,6 +34,17 @@ export const PageSummary: React.FC = ({ entries }: PageSummary return () => window.removeEventListener('sourceset-filter-change', handeEvent) }, [entries]) + const handleMouseHover = () => { + setHidden(!hidden) + } + + const handleClick = (entry: PageSummaryEntry) => { + document.getElementById(containerId).scrollTo({ + top: document.getElementById(entry.location).offsetTop - topOffset, + behavior: 'smooth' + }) + } + let classnames = "page-summary" if (hidden) classnames += " hidden" @@ -45,7 +57,7 @@ export const PageSummary: React.FC = ({ entries }: PageSummary

On this page

{!hidden && e.location)} currentClassName="selected"> - {displayableEntries.map((item) =>
  • {item.label}
  • )} + {displayableEntries.map((item) =>
  • handleClick(item)}>{item.label}
  • )}
    }
    diff --git a/plugins/base/frontend/src/main/components/root.tsx b/plugins/base/frontend/src/main/components/root.tsx index cb070dfb..4161a4c1 100644 --- a/plugins/base/frontend/src/main/components/root.tsx +++ b/plugins/base/frontend/src/main/components/root.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { render } from 'react-dom'; -import RedBox from 'redbox-react'; import _ from "lodash"; import App from "./app"; @@ -8,9 +7,6 @@ import './app/index.scss'; import { NavigationPaneSearch } from './navigationPaneSearch/navigationPaneSearch'; import { PageSummary } from './pageSummary/pageSummary'; -const appEl = document.getElementById('searchBar'); -const rootEl = document.createElement('div'); - const renderNavigationPane = () => { render( , @@ -19,57 +15,33 @@ const renderNavigationPane = () => { } const renderOnThisPage = () => { - document.addEventListener('DOMContentLoaded', () => { - for (const e of document.querySelectorAll('.tabs-section-body > div[data-togglable]')) { - const entries = Array.from(e.querySelectorAll('a[anchor-label]')).map((element: HTMLElement) => { - return { - location: element.getAttribute('data-name'), - label: element.getAttribute('anchor-label'), - sourceSets: _.sortBy(element.getAttribute('data-filterable-set').split(' ')) - } - }) - const unique = _.uniqBy(entries, ({label}) => label) - if (unique.length) { - const element = document.createElement('div') - render(, element) - e.appendChild(element) + for (const e of document.querySelectorAll('.tabs-section-body > div[data-togglable]')) { + const entries = Array.from(e.querySelectorAll('a[anchor-label]')).map((element: HTMLElement) => { + return { + location: element.getAttribute('data-name'), + label: element.getAttribute('anchor-label'), + sourceSets: _.sortBy(element.getAttribute('data-filterable-set').split(' ')) } + }) + const unique = _.uniqBy(entries, ({label}) => label) + if (unique.length) { + const element = document.createElement('div') + render(, element) + e.appendChild(element) } - }) + } +} + +const renderMainSearch = () => { + render(, document.getElementById('searchBar')); } let renderApp = () => { - render( - , - rootEl - ); + renderMainSearch(); renderNavigationPane(); renderOnThisPage(); -}; -// @ts-ignore -if (module.hot) { - const renderAppHot = renderApp; - const renderError = (error: Error) => { - render( - , - rootEl - ); - }; - - renderApp = () => { - try { - renderAppHot(); - } catch (error) { - renderError(error); - } - }; - - // @ts-ignore - module.hot.accept('./app', () => { - setTimeout(renderApp); - }); -} + document.removeEventListener('DOMContentLoaded', renderApp); +}; -renderApp(); -appEl!.appendChild(rootEl); +document.addEventListener('DOMContentLoaded', renderApp); \ No newline at end of file diff --git a/plugins/base/frontend/src/main/components/search/search.scss b/plugins/base/frontend/src/main/components/search/search.scss index e7e7673d..8965c5ea 100644 --- a/plugins/base/frontend/src/main/components/search/search.scss +++ b/plugins/base/frontend/src/main/components/search/search.scss @@ -4,6 +4,7 @@ fill: #637282; background: #F4F4F4; cursor: pointer; + margin-top: 3px; &:focus { outline: none; diff --git a/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt b/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt index 05eda494..615bbfc3 100644 --- a/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt +++ b/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt @@ -99,8 +99,7 @@ open class HtmlRenderer( node.hasStyle(TextStyle.Span) -> span() { childrenCallback() } node.dci.kind == ContentKind.Symbol -> div("symbol $additionalClasses") { childrenCallback() } node.dci.kind == ContentKind.BriefComment -> div("brief $additionalClasses") { childrenCallback() } - node.dci.kind == ContentKind.Cover -> div("cover $additionalClasses") { - filterButtons(pageContext) + node.dci.kind == ContentKind.Cover -> div("cover $additionalClasses") { //TODO this can be removed childrenCallback() } node.hasStyle(TextStyle.Paragraph) -> p(additionalClasses) { childrenCallback() } @@ -110,8 +109,8 @@ open class HtmlRenderer( } } - private fun FlowContent.filterButtons(page: ContentPage) { - if (shouldRenderSourceSetBubbles) { + private fun FlowContent.filterButtons(page: PageNode) { + if (shouldRenderSourceSetBubbles && page is ContentPage) { div(classes = "filter-section") { id = "filter-section" page.content.withDescendants().flatMap { it.sourceSets }.distinct().forEach { @@ -584,13 +583,22 @@ open class HtmlRenderer( override fun FlowContent.buildNavigation(page: PageNode) = - div(classes = "breadcrumbs") { - val path = locationProvider.ancestors(page).filterNot { it is RendererSpecificPage }.asReversed() - if (path.isNotEmpty()) { - buildNavigationElement(path.first(), page) - path.drop(1).forEach { node -> - text("/") - buildNavigationElement(node, page) + div("navigation-wrapper") { + id = "navigation-wrapper" + div(classes = "breadcrumbs") { + val path = locationProvider.ancestors(page).filterNot { it is RendererSpecificPage }.asReversed() + if (path.isNotEmpty()) { + buildNavigationElement(path.first(), page) + path.drop(1).forEach { node -> + text("/") + buildNavigationElement(node, page) + } + } + } + div("pull-right d-flex") { + filterButtons(page) + div { + id = "searchBar" } } } @@ -706,7 +714,7 @@ open class HtmlRenderer( override fun buildPage(page: ContentPage, content: (FlowContent, ContentPage) -> Unit): String = buildHtml(page, page.embeddedResources) { - div { + div("main-content") { id = "content" attributes["pageIds"] = page.pageId content(this, page) @@ -762,15 +770,12 @@ open class HtmlRenderer( id = "leftToggler" span("icon-toggler") } - div { - id = "searchBar" - } script(type = ScriptType.textJavaScript, src = page.root("scripts/pages.js")) {} script(type = ScriptType.textJavaScript, src = page.root("scripts/main.js")) {} content() div(classes = "footer") { span("go-to-top-icon") { - a(href = "#container") + a(href = "#content") } span { text("© 2020 Copyright") } span("pull-right") { diff --git a/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt b/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt index 93fe8221..8e31fbc6 100644 --- a/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt +++ b/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt @@ -85,7 +85,7 @@ object ScriptsInstaller : PageTransformer { "scripts/clipboard.js", "scripts/navigation-loader.js", "scripts/platform-content-handler.js", - "scripts/main.js" + "scripts/main.js", ) override fun invoke(input: RootPageNode): RootPageNode { diff --git a/plugins/base/src/main/resources/dokka/scripts/platform-content-handler.js b/plugins/base/src/main/resources/dokka/scripts/platform-content-handler.js index 26dd9424..07a6642b 100644 --- a/plugins/base/src/main/resources/dokka/scripts/platform-content-handler.js +++ b/plugins/base/src/main/resources/dokka/scripts/platform-content-handler.js @@ -4,6 +4,8 @@ filteringContext = { activeFilters: [] } let highlightedAnchor; +let topNavbarOffset; +var scrollNavbarBreakPoint = 300 window.addEventListener('load', () => { document.querySelectorAll("div[data-platform-hinted]") @@ -18,6 +20,13 @@ window.addEventListener('load', () => { initTabs() handleAnchor() initHidingLeftNavigation() + + document.getElementById('main').addEventListener("scroll", (e) => { + const element = document.getElementsByClassName("navigation-wrapper")[0] + const additionalOffset = element.classList.contains("sticky-navigation") ? 14 : 0 + element.classList.toggle("sticky-navigation", e.target.scrollTop + additionalOffset > scrollNavbarBreakPoint) + }) + topNavbarOffset = document.getElementById('navigation-wrapper') }) const initHidingLeftNavigation = () => { @@ -64,7 +73,24 @@ function handleAnchor() { content.classList.add('anchor-highlight') highlightedAnchor = content } - element.scrollIntoView({behavior: "smooth"}) + + const scrollToElement = () => document.getElementById('main').scrollTo({ top: element.offsetTop - topNavbarOffset.offsetHeight, behavior: "smooth"}) + + const waitAndScroll = () => { + setTimeout(() => { + if(topNavbarOffset){ + scrollToElement() + } else { + waitForScroll() + } + }, 100) + } + + if(topNavbarOffset){ + scrollToElement() + } else { + waitAndScroll() + } } } } diff --git a/plugins/base/src/main/resources/dokka/styles/style.css b/plugins/base/src/main/resources/dokka/styles/style.css index 5e505f33..9e843fe1 100644 --- a/plugins/base/src/main/resources/dokka/styles/style.css +++ b/plugins/base/src/main/resources/dokka/styles/style.css @@ -11,16 +11,68 @@ --horizontal-spacing-for-content: 42px; --mobile-horizontal-spacing-for-content: 8px; --bottom-spacing: 16px; + --color-scrollbar: rgba(39, 40, 44, 0.40); + --color-scrollbar-track: #f4f4f4; } -#content { - padding: 0 var(--horizontal-spacing-for-content) var(--bottom-spacing) var(--horizontal-spacing-for-content); +html { + height: 100%; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + scrollbar-color: rgba(39, 40, 44, 0.40) #F4F4F4; + scrollbar-color: var(--color-scrollbar) var(--color-scrollbar-track); } -.breadcrumbs { +html ::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +html ::-webkit-scrollbar-track { + background-color: var(--color-scrollbar-track); +} + +html ::-webkit-scrollbar-thumb { + width: 8px; + border-radius: 6px; + background: rgba(39, 40, 44, 0.40); + background: var(--color-scrollbar); +} + + +.main-content { + padding-bottom: var(--bottom-spacing); + z-index: 0; +} + +.main-content>* { + margin-left: var(--horizontal-spacing-for-content); + margin-right: var(--horizontal-spacing-for-content); +} + +.navigation-wrapper { + display: flex; padding: 24px 0; + flex-wrap: wrap; +} + +.navigation-wrapper.sticky-navigation { + position: sticky; + top: 0; + background-color: #f4f4f4; + border-bottom: 1px solid #DADFE6; + padding-top: 19px; + padding-bottom: 18px; + z-index: 8; + + /* Reset margin and use padding for border */ + margin-left: 0; + margin-right: 0; + padding-left: var(--horizontal-spacing-for-content); + padding-right: var(--horizontal-spacing-for-content); +} + +.breadcrumbs { color: var(--breadcrumb-font-color); - max-width: calc(100% - 32px); overflow-wrap: break-word; } @@ -93,7 +145,6 @@ .cover { display: flex; flex-direction: column; - width: 100%; } .cover .platform-hinted .sourceset-depenent-content > .symbol, @@ -145,10 +196,6 @@ border-bottom: 2px solid white; } -.title { - overflow-x: auto; -} - .title > .divergent-group:first-of-type { padding-top: 0; } @@ -157,6 +204,7 @@ display: flex; flex-direction: row; min-height: 100%; + height: 100%; } #main { @@ -164,6 +212,8 @@ max-width: calc(100% - 280px); display: flex; flex-direction: column; + height: auto; + overflow: auto; } #leftColumn { @@ -177,6 +227,8 @@ padding-top: 16px; position: relative; max-height: calc(100% - 140px); + height: 100%; + overflow-y: auto; } #sideMenu img { @@ -187,10 +239,6 @@ background: #DADFE6; } -#searchBar { - float: right; -} - #logo { background-size: 125px 26px; border-bottom: 1px solid #DADFE6; @@ -464,9 +512,6 @@ h1.cover { line-height: 64px; letter-spacing: -1.5px; - margin-left: calc(-1 * var(--horizontal-spacing-for-content)); - margin-right: calc(-1 * var(--horizontal-spacing-for-content)); - padding-left: var(--horizontal-spacing-for-content); border-bottom: 1px solid #DADFE6; margin-bottom: 0; @@ -720,11 +765,7 @@ footer { flex-direction: row; align-self: flex-end; min-height: 30px; - position: absolute; - top: 20px; - right: 88px; z-index: 0; - flex-wrap: wrap; } .platform-selector:hover { @@ -1039,7 +1080,7 @@ td.content { display: flex; align-items: center; position: relative; - height: var(--footer-height); + min-height: var(--footer-height); border-top: 1px solid #DADFE6; font-size: 12px; line-height: 16px; @@ -1105,11 +1146,11 @@ div.runnablesample { padding: 0; } +.d-flex { + display: flex; +} + @media screen and (max-width: 1119px) { - /* TODO this feels wrong as only is an aproximation of correct solution*/ - .filter-section { - position: unset; - } h1.cover { font-size: 48px; line-height: 48px; @@ -1166,10 +1207,16 @@ div.runnablesample { padding-right: 0.5em; margin-left: -0.5em; } - #content { - padding: var(--mobile-horizontal-spacing-for-content); - padding-top: 0; + .main-content > * { + margin-left: var(--mobile-horizontal-spacing-for-content); + margin-right: var(--mobile-horizontal-spacing-for-content); } + + .navigation-wrapper.sticky-navigation { + padding-left: var(--mobile-horizontal-spacing-for-content); + padding-right: var(--mobile-horizontal-spacing-for-content); + } + #sideMenu { padding-bottom: 16px; overflow: auto; -- cgit