diff options
Diffstat (limited to 'plugins/base')
13 files changed, 210 insertions, 29 deletions
diff --git a/plugins/base/frontend/package-lock.json b/plugins/base/frontend/package-lock.json index ed81a875..ef4bd987 100644 --- a/plugins/base/frontend/package-lock.json +++ b/plugins/base/frontend/package-lock.json @@ -1582,6 +1582,14 @@ "@types/react": "*" } }, + "@types/react-scrollspy": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@types/react-scrollspy/-/react-scrollspy-3.3.3.tgz", + "integrity": "sha512-HLaQStNl141NngJoczBNAK71GRmmbO25p+JiJyEI0bUvH9MKO8B52SI3kVb1PU48S/n1SiNbabDn3D1p37/1bA==", + "requires": { + "@types/react": "*" + } + }, "@types/unist": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.3.tgz", @@ -10570,6 +10578,16 @@ "xtend": "^4.0.1" } }, + "react-scrollspy": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/react-scrollspy/-/react-scrollspy-3.4.3.tgz", + "integrity": "sha512-c2QZpMPWxm1HF71h1EqaxBldx2zLYO0aZ24Bcuo2mUWF79T+F6qOtr7XJCzUDm99NOwhVKQD01a7A8VC6c90CQ==", + "requires": { + "babel-runtime": "^6.26.0", + "classnames": "^2.2.5", + "prop-types": "^15.5.10" + } + }, "react-sortable-hoc": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/react-sortable-hoc/-/react-sortable-hoc-1.11.0.tgz", diff --git a/plugins/base/frontend/package.json b/plugins/base/frontend/package.json index c86d324d..90e9d49c 100644 --- a/plugins/base/frontend/package.json +++ b/plugins/base/frontend/package.json @@ -30,12 +30,14 @@ "@types/node": "^12.12.36", "@types/react": "^16.9.0", "@types/react-dom": "^16.9.0", + "@types/react-scrollspy": "^3.3.3", "babel-loader": "^8.0.6", "lodash": "^4.17.19", "postcss-import": "^12.0.1", "postcss-preset-env": "^6.7.0", "react": "^16.12.0", "react-dom": "^16.12.0", + "react-scrollspy": "^3.4.3", "redbox-react": "^1.6.0", "ts-loader": "^7.0.0", "typescript": "^3.8.3", diff --git a/plugins/base/frontend/src/main/components/assets/clear.svg b/plugins/base/frontend/src/main/components/assets/clear.svg new file mode 100644 index 00000000..ddb8450f --- /dev/null +++ b/plugins/base/frontend/src/main/components/assets/clear.svg @@ -0,0 +1,3 @@ +<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M11.1374 1.80464L6.94205 5.99996L11.1374 10.1953L10.1947 11.138L5.99935 6.94267L1.80403 11.138L0.861328 10.1953L5.05664 5.99996L0.861328 1.80464L1.80403 0.861938L5.99935 5.05725L10.1947 0.861938L11.1374 1.80464Z" fill="#637282"/> +</svg>
\ No newline at end of file diff --git a/plugins/base/frontend/src/main/components/search/searchIcon.svg b/plugins/base/frontend/src/main/components/assets/searchIcon.svg index 391b1cab..391b1cab 100644 --- a/plugins/base/frontend/src/main/components/search/searchIcon.svg +++ b/plugins/base/frontend/src/main/components/assets/searchIcon.svg diff --git a/plugins/base/frontend/src/main/components/pageSummary/pageSummary.scss b/plugins/base/frontend/src/main/components/pageSummary/pageSummary.scss new file mode 100644 index 00000000..400631e6 --- /dev/null +++ b/plugins/base/frontend/src/main/components/pageSummary/pageSummary.scss @@ -0,0 +1,70 @@ +@import "src/main/scss/index.scss"; + +.page-summary { + background: $white; + border: 1px solid $grey-border; + position: fixed; + top: 25%; + max-height: 70vh; + right: -2px; + width: 250px; + z-index: 8; + transition: width .2s; + + &.hidden { + width: 3em; + writing-mode: vertical-rl; + text-orientation: mixed; + + .content-wrapper { + h4 { + margin: 0; + padding: 8px; + } + } + } + + .content-wrapper { + padding: 0.5em 0 1em 0; + letter-spacing: 0.2px; + + h4 { + margin: 0 2em; + font-weight: 600; + } + + ul { + list-style-type: none; + width: 100%; + padding: 0; + margin: 1em 0; + overflow-x: hidden; + overflow-y: auto; + max-height: 60vh; + + li { + width: 100%; + padding: 4px 0; + + &:hover { + background: $list-background-hover; + } + + &>a { + margin: 0 2em; + } + + &.selected { + border-left: 4px solid $hover-link-color; + } + } + } + } +} + +@media screen and (max-width: 759px){ + /* hide it on smaller screens since it looks super weird when displayed with hidden menu */ + .page-summary { + display: none; + } +}
\ No newline at end of file diff --git a/plugins/base/frontend/src/main/components/pageSummary/pageSummary.tsx b/plugins/base/frontend/src/main/components/pageSummary/pageSummary.tsx new file mode 100644 index 00000000..1aa65d9b --- /dev/null +++ b/plugins/base/frontend/src/main/components/pageSummary/pageSummary.tsx @@ -0,0 +1,53 @@ +import React, { useState, useEffect } from "react"; +import './pageSummary.scss' +import _ from "lodash"; +import Scrollspy from 'react-scrollspy' + +type PageSummaryProps = { + entries: PageSummaryEntry[], +} + +type PageSummaryEntry = { + location: string, + label: string, + sourceSets: SourceSetFilterKey[] +} + +type SourceSetFilterKey = string + +export const PageSummary: React.FC<PageSummaryProps> = ({ entries }: PageSummaryProps) => { + const [hidden, setHidden] = useState<Boolean>(true); + const [displayableEntries, setDisplayableEntries] = useState<PageSummaryEntry[]>(entries) + + const handleMouseHover = () => { + setHidden(!hidden) + } + + useEffect(() => { + const handeEvent = (event: CustomEvent<SourceSetFilterKey[]>) => { + const displayable = entries.filter((entry) => entry.sourceSets.some((sourceset) => event.detail.includes(sourceset))) + setDisplayableEntries(displayable) + } + + window.addEventListener('sourceset-filter-change', handeEvent) + return () => window.removeEventListener('sourceset-filter-change', handeEvent) + }, [entries]) + + let classnames = "page-summary" + if (hidden) classnames += " hidden" + + return ( + <div + className={classnames} + onMouseEnter={handleMouseHover} + onMouseLeave={handleMouseHover} + > + <div className={"content-wrapper"}> + <h4>On this page</h4> + {!hidden && <Scrollspy items={displayableEntries.map((e) => e.location)} currentClassName="selected"> + {displayableEntries.map((item) => <li><a href={'#' + item.location}>{item.label}</a></li>)} + </Scrollspy>} + </div> + </div> + ) +}
\ No newline at end of file diff --git a/plugins/base/frontend/src/main/components/root.tsx b/plugins/base/frontend/src/main/components/root.tsx index ad93d731..cb070dfb 100644 --- a/plugins/base/frontend/src/main/components/root.tsx +++ b/plugins/base/frontend/src/main/components/root.tsx @@ -1,30 +1,50 @@ import React from 'react'; -import {render} from 'react-dom'; +import { render } from 'react-dom'; import RedBox from 'redbox-react'; +import _ from "lodash"; import App from "./app"; 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 = () => { - const element = document.getElementById('paneSearch') - if(element){ - render( - <NavigationPaneSearch />, - document.getElementById('paneSearch') - ) - } + render( + <NavigationPaneSearch />, + document.getElementById('paneSearch') + ) +} + +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(<PageSummary entries={unique} />, element) + e.appendChild(element) + } + } + }) } let renderApp = () => { render( - <App/>, - rootEl + <App />, + rootEl ); renderNavigationPane(); + renderOnThisPage(); }; // @ts-ignore @@ -32,8 +52,8 @@ if (module.hot) { const renderAppHot = renderApp; const renderError = (error: Error) => { render( - <RedBox error={error}/>, - rootEl + <RedBox error={error} />, + rootEl ); }; diff --git a/plugins/base/frontend/src/main/components/search/dokkaSearchAnchor.tsx b/plugins/base/frontend/src/main/components/search/dokkaSearchAnchor.tsx index ad0b5f8f..a502f589 100644 --- a/plugins/base/frontend/src/main/components/search/dokkaSearchAnchor.tsx +++ b/plugins/base/frontend/src/main/components/search/dokkaSearchAnchor.tsx @@ -1,5 +1,5 @@ import React from "react"; -import SearchIcon from 'react-svg-loader!./searchIcon.svg'; +import SearchIcon from 'react-svg-loader!../assets/searchIcon.svg'; export const DokkaSearchAnchor = ({wrapperProps, buttonProps, popup}: any) => { return ( diff --git a/plugins/base/frontend/src/main/scss/index.scss b/plugins/base/frontend/src/main/scss/index.scss index 18e2861b..4f5a498b 100644 --- a/plugins/base/frontend/src/main/scss/index.scss +++ b/plugins/base/frontend/src/main/scss/index.scss @@ -1,4 +1,6 @@ @import "~@jetbrains/ring-ui/components/global/variables.css"; $white: #FFFFFF; -$grey-border: #A6AFBA +$grey-border: #DADFE6; +$list-background-hover: rgba(91, 93, 239, 0.15); +$hover-link-color: #5B5DEF; diff --git a/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt b/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt index 07cba90c..8f6129a6 100644 --- a/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt +++ b/plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt @@ -13,6 +13,7 @@ import org.jetbrains.dokka.base.resolvers.anchors.SymbolAnchorHint import org.jetbrains.dokka.base.transformers.pages.sourcelinks.hasTabbedContent import org.jetbrains.dokka.base.renderers.isImage import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.model.CompositeSourceSetID import org.jetbrains.dokka.model.DisplaySourceSet import org.jetbrains.dokka.model.properties.PropertyContainer import org.jetbrains.dokka.model.sourceSetIDs @@ -105,7 +106,7 @@ open class HtmlRenderer( } node.hasStyle(TextStyle.Paragraph) -> p(additionalClasses) { childrenCallback() } node.hasStyle(TextStyle.Block) -> div(additionalClasses) { childrenCallback() } - node.isAnchorable -> buildAnchor(node.anchor, node.anchorLabel!!) { childrenCallback() } + node.isAnchorable -> buildAnchor(node.anchor!!, node.anchorLabel!!, node.sourceSetsFilters) { childrenCallback() } else -> childrenCallback() } } @@ -379,7 +380,7 @@ open class HtmlRenderer( sourceSetRestriction: Set<DisplaySourceSet>?, style: Set<Style> ) { - anchorFromNode(contextNode) + buildAnchor(contextNode) div(classes = "table-row") { div("main-subrow " + contextNode.style.joinToString(separator = " ")) { buildRowHeaderLink(toRender, pageContext, sourceSetRestriction, contextNode.anchor, "w-100") @@ -397,7 +398,7 @@ open class HtmlRenderer( sourceSetRestriction: Set<DisplaySourceSet>?, style: Set<Style> ) { - anchorFromNode(contextNode) + buildAnchor(contextNode) div(classes = "table-row") { addSourceSetFilteringAttributes(contextNode) div { @@ -423,7 +424,7 @@ open class HtmlRenderer( sourceSetRestriction: Set<DisplaySourceSet>?, style: Set<Style> ) { - anchorFromNode(contextNode) + buildAnchor(contextNode) div(classes = "table-row") { addSourceSetFilteringAttributes(contextNode) div("main-subrow keyValue " + contextNode.style.joinToString(separator = " ")) { @@ -449,7 +450,7 @@ open class HtmlRenderer( toRender: List<ContentNode>, pageContext: ContentPage, sourceSetRestriction: Set<DisplaySourceSet>?, - anchorDestination: String, + anchorDestination: String?, classes: String = "" ) { toRender.filter { it is ContentLink || it.hasStyle(ContentStyle.RowTitle) }.takeIf { it.isNotEmpty() }?.let { @@ -458,7 +459,7 @@ open class HtmlRenderer( .forEach { span("inline-flex") { it.build(this, pageContext, sourceSetRestriction) - if(it is ContentLink) buildAnchorCopyButton(anchorDestination) + if(it is ContentLink && !anchorDestination.isNullOrBlank()) buildAnchorCopyButton(anchorDestination) } } } @@ -581,19 +582,21 @@ open class HtmlRenderer( } } - private fun FlowContent.buildAnchor(anchor: String, anchorLabel: String, content: FlowContent.() -> Unit) { + private fun FlowContent.buildAnchor(anchor: String, anchorLabel: String, sourceSets: String, content: FlowContent.() -> Unit) { a { attributes["data-name"] = anchor attributes["anchor-label"] = anchorLabel + attributes["id"] = anchor + attributes["data-filterable-set"] = sourceSets } content() } - private fun FlowContent.buildAnchor(anchor: String, anchorLabel: String) = - buildAnchor(anchor, anchorLabel) {} + private fun FlowContent.buildAnchor(anchor: String, anchorLabel: String, sourceSets: String) = + buildAnchor(anchor, anchorLabel, sourceSets) {} - private fun FlowContent.anchorFromNode(node: ContentNode) { - node.anchorLabel?.let { label -> buildAnchor(node.anchor, label) } + private fun FlowContent.buildAnchor(node: ContentNode) { + node.anchorLabel?.let { label -> buildAnchor(node.anchor!!, label, node.sourceSetsFilters) } } @@ -846,5 +849,8 @@ val ContentNode.isAnchorable: Boolean val ContentNode.anchorLabel: String? get() = extra[SymbolAnchorHint]?.anchorName -val ContentNode.anchor: String - get() = (dci.dri.first().toString() + "/" + extra[SymbolAnchorHint]?.contentKind + "/" + sourceSets.joinToString { it.sourceSetIDs.all.joinToString() }).urlEncoded() +val ContentNode.anchor: String? + get() = extra[SymbolAnchorHint]?.contentKind?.let { contentKind -> (dci.dri.first().toString() + "/" + contentKind + "/" + sourceSets.joinToString { it.sourceSetIDs.all.joinToString() }).urlEncoded() } + +val ContentNode.sourceSetsFilters: String + get() = sourceSets.sourceSetIDs.joinToString(" ") { it.toString() } diff --git a/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt b/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt index 6c3c0330..4582fd67 100644 --- a/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt +++ b/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt @@ -544,7 +544,7 @@ open class DefaultPageCreator( ) { elements.map { instance(setOf(it.dri), it.sourceSets.toSet(), extra = PropertyContainer.withAll(SymbolAnchorHint(it.name ?: "", kind))) { - divergent { + divergent(extra = PropertyContainer.empty()) { group { +buildSignature(it) } diff --git a/plugins/base/src/main/resources/dokka/scripts/navigation-loader.js b/plugins/base/src/main/resources/dokka/scripts/navigation-loader.js index c2f60ec5..0d9948ad 100644 --- a/plugins/base/src/main/resources/dokka/scripts/navigation-loader.js +++ b/plugins/base/src/main/resources/dokka/scripts/navigation-loader.js @@ -16,7 +16,7 @@ window.addEventListener('load', () => { }) /* Smooth scrolling support for going to the top of the page */ - document.querySelectorAll('a[href^="#"]').forEach(anchor => { + document.querySelectorAll('.footer a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', function (e) { e.preventDefault(); 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 022aca4f..26dd9424 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 @@ -114,6 +114,10 @@ function initializeFiltering() { let cached = window.localStorage.getItem('inactive-filters') if (cached) { let parsed = JSON.parse(cached) + //Events are used by react to get values in 'on this page' + const event = new CustomEvent('sourceset-filter-change', { detail: parsed }); + window.dispatchEvent(event); + filteringContext.activeFilters = filteringContext.restrictedDependencies .filter(q => parsed.indexOf(q) == -1 ) } else { @@ -215,6 +219,9 @@ function refreshFiltering() { elem.setAttribute("data-filterable-current", platformList.join(' ')) } ) + const event = new CustomEvent('sourceset-filter-change', { detail: sourcesetList }); + window.dispatchEvent(event); + refreshFilterButtons() refreshPlatformTabs() } |