This reverts commit 72869ae848
.
tags/7.5
@@ -20,47 +20,15 @@ | |||
import * as React from 'react'; | |||
export default class HeadingsLink extends React.Component { | |||
skipScrollingHandler = false; | |||
constructor(props) { | |||
super(props); | |||
this.state = { | |||
activeIndex: -1, | |||
headers: props.headers.filter( | |||
h => h.depth === 2 && h.value.toLowerCase() !== 'table of contents' | |||
) | |||
}; | |||
} | |||
componentDidMount() { | |||
document.addEventListener('scroll', this.scrollHandler, true); | |||
} | |||
componentWillReceiveProps(nextProps) { | |||
this.setState({ | |||
headers: nextProps.headers.filter( | |||
h => h.depth === 2 && h.value.toLowerCase() !== 'table of contents' | |||
) | |||
}); | |||
} | |||
componentWillUnmount() { | |||
document.removeEventListener('scroll', this.scrollHandler, true); | |||
} | |||
highlightHeading = scrollTop => { | |||
let headingIndex = 0; | |||
for (let i = 0; i < this.state.headers.length; i++) { | |||
if (document.querySelector('#header-' + (i + 1)).offsetTop > scrollTop + 40) { | |||
break; | |||
} | |||
headingIndex = i; | |||
} | |||
this.setState({ activeIndex: headingIndex }); | |||
this.markH2(headingIndex + 1, false); | |||
}; | |||
markH2 = (index, scrollTo) => { | |||
highlightHeading = (index, scrollTo) => { | |||
const previousNode = document.querySelector('.targetted-heading'); | |||
if (previousNode) { | |||
previousNode.classList.remove('targetted-heading'); | |||
@@ -70,33 +38,38 @@ export default class HeadingsLink extends React.Component { | |||
if (node) { | |||
node.classList.add('targetted-heading'); | |||
if (scrollTo) { | |||
this.skipScrollingHandler = true; | |||
window.scrollTo(0, node.offsetTop - 30); | |||
this.highlightHeading(node.offsetTop - 30); | |||
} | |||
} | |||
}; | |||
scrollHandler = () => { | |||
if (this.skipScrollingHandler) { | |||
this.skipScrollingHandler = false; | |||
return; | |||
} | |||
const headings = Array.from(document.querySelectorAll('.headings-container ul li a')); | |||
const scrollTop = window.pageYOffset | document.body.scrollTop; | |||
this.highlightHeading(scrollTop); | |||
let headingIndex = 0; | |||
for (let i = 0; i < headings.length; i++) { | |||
if (document.querySelector('#header-' + (i + 1)).offsetTop > scrollTop + 40) { | |||
break; | |||
} | |||
headingIndex = i; | |||
} | |||
headings.forEach(h => h.classList.remove('active')); | |||
headings[headingIndex].classList.add('active'); | |||
this.highlightHeading(headingIndex + 1, false); | |||
}; | |||
clickHandler = target => { | |||
return event => { | |||
event.stopPropagation(); | |||
event.preventDefault(); | |||
this.markH2(target, true); | |||
this.highlightHeading(target, true); | |||
}; | |||
}; | |||
render() { | |||
const { headers } = this.state; | |||
const headers = this.props.headers.filter( | |||
h => h.depth === 2 && h.value.toLowerCase() !== 'table of contents' | |||
); | |||
if (headers.length < 1) { | |||
return null; | |||
} | |||
@@ -107,10 +80,7 @@ export default class HeadingsLink extends React.Component { | |||
{headers.map((header, index) => { | |||
return ( | |||
<li key={index + 1}> | |||
<a | |||
onClick={this.clickHandler(index + 1)} | |||
href={'#header-' + (index + 1)} | |||
className={this.state.activeIndex === index ? 'active' : ''}> | |||
<a onClick={this.clickHandler(index + 1)} href={'#header-' + (index + 1)}> | |||
{header.value} | |||
</a> | |||
</li> |
@@ -19,16 +19,13 @@ | |||
*/ | |||
import React, { Component } from 'react'; | |||
import lunr, { LunrIndex } from 'lunr'; | |||
import ClearIcon from './icons/ClearIcon'; | |||
// Search component | |||
export default class Search extends Component { | |||
index = null; | |||
input = null; | |||
constructor(props) { | |||
super(props); | |||
this.state = { value: '' }; | |||
this.index = lunr(function() { | |||
this.ref('id'); | |||
this.field('title', { boost: 10 }); | |||
@@ -40,7 +37,7 @@ export default class Search extends Component { | |||
this.add({ | |||
id: page.id, | |||
title: page.frontmatter.title, | |||
text: page.html.replace(/<(?:.|\n)*?>/gm, '').replace(/<(?:.|\n)*?>/gm, '') | |||
text: page.html.replace(/<(?:.|\n)*?>/gm, '') | |||
}) | |||
); | |||
}); | |||
@@ -69,7 +66,7 @@ export default class Search extends Component { | |||
id: page.id, | |||
slug: page.fields.slug, | |||
title: page.frontmatter.title, | |||
text: page.html.replace(/<(?:.|\n)*?>/gm, '').replace(/<(?:.|\n)*?>/gm, '') | |||
text: page.html.replace(/<(?:.|\n)*?>/gm, '') | |||
}, | |||
highlights, | |||
longestTerm | |||
@@ -77,43 +74,25 @@ export default class Search extends Component { | |||
}); | |||
}; | |||
handleClear = event => { | |||
this.setState({ value: '' }); | |||
this.props.onResultsChange([], ''); | |||
if (this.input) { | |||
this.input.focus(); | |||
} | |||
}; | |||
handleChange = event => { | |||
const { value } = event.currentTarget; | |||
this.setState({ value }); | |||
if (value != '') { | |||
const results = this.getFormattedResults(value, this.index.search(`${value}~1 ${value}*`)); | |||
this.props.onResultsChange(results, value); | |||
this.props.onResultsChange(results); | |||
} else { | |||
this.props.onResultsChange([], value); | |||
this.props.onResultsChange([]); | |||
} | |||
}; | |||
render() { | |||
return ( | |||
<div className="search-container"> | |||
<input | |||
aria-label="Search" | |||
className="search-input" | |||
onChange={this.handleChange} | |||
placeholder="Search..." | |||
ref={node => (this.input = node)} | |||
type="search" | |||
value={this.state.value} | |||
/> | |||
{this.state.value && ( | |||
<button onClick={this.handleClear}> | |||
<ClearIcon size="8" /> | |||
</button> | |||
)} | |||
</div> | |||
<input | |||
aria-label="Search" | |||
className="search-input" | |||
onChange={this.handleChange} | |||
placeholder="Search..." | |||
type="search" | |||
/> | |||
); | |||
} | |||
} |
@@ -27,7 +27,7 @@ import Search from './Search'; | |||
import SearchEntryResult from './SearchEntryResult'; | |||
export default class Sidebar extends React.PureComponent { | |||
state = { loaded: false, query: '', results: [], versions: [] }; | |||
state = { loaded: false, results: [], versions: [] }; | |||
componentDidMount() { | |||
this.loadVersions(); | |||
@@ -74,8 +74,8 @@ export default class Sidebar extends React.PureComponent { | |||
); | |||
}; | |||
handleSearch = (results, query) => { | |||
this.setState({ results, query }); | |||
handleSearch = results => { | |||
this.setState({ results }); | |||
}; | |||
render() { | |||
@@ -111,8 +111,8 @@ export default class Sidebar extends React.PureComponent { | |||
</div> | |||
<div className="page-indexes"> | |||
<Search pages={this.props.pages} onResultsChange={this.handleSearch} /> | |||
{this.state.query !== '' && this.renderResults()} | |||
{this.state.query === '' && | |||
{this.state.results.length > 0 && this.renderResults()} | |||
{this.state.results.length === 0 && | |||
Object.keys(nodes).map(key => ( | |||
<CategoryLink | |||
key={key} |
@@ -1,32 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import Icon from './Icon'; | |||
export default function ClearIcon({ className, fill = 'currentColor', size }) { | |||
return ( | |||
<Icon className={className} size={size} viewBox="0 0 48 48"> | |||
<path | |||
d="M28.24 24L47.07 5.16A3 3 0 1 0 42.93.83l-.09.1L24 19.76 5.16.93A3 3 0 0 0 .93 5.16L19.76 24 .93 42.84a3 3 0 1 0 4.14 4.33l.09-.1L24 28.24l18.84 18.83a3 3 0 1 0 4.33-4.14l-.1-.09z" | |||
style={{ fill }} | |||
/> | |||
</Icon> | |||
); | |||
} |
@@ -131,52 +131,13 @@ body > div, | |||
} | |||
} | |||
.search-container { | |||
position: relative; | |||
} | |||
.search-container button { | |||
position: absolute; | |||
right: 8px; | |||
top: 50%; | |||
margin-top: -12px; | |||
height: 16px; | |||
width: 16px; | |||
background: transparent; | |||
border: none; | |||
cursor: pointer; | |||
outline: none; | |||
border-radius: 3px; | |||
transition: border-color 0.2s ease, box-shadow 0.2s ease; | |||
} | |||
.search-container button svg { | |||
position: absolute; | |||
top: 4px; | |||
left: 4px; | |||
} | |||
.search-container button:hover, | |||
.search-container button:focus { | |||
background-color: #989898; | |||
} | |||
.search-container button:hover svg, | |||
.search-container button:focus svg { | |||
color: #fff; | |||
} | |||
.search-container button:focus { | |||
box-shadow: 0 0 0 3px rgba(35, 106, 151, 0.25); | |||
} | |||
.search-input { | |||
border: 1px solid #cfd3d7; | |||
border-radius: 2px; | |||
width: calc(100% - 10px); | |||
margin-left: 10px; | |||
margin-bottom: 10px; | |||
padding: 0 30px 0 10px; | |||
padding: 0 10px; | |||
font-size: 14px; | |||
line-height: 30px; | |||
outline: none; | |||
@@ -327,7 +288,6 @@ a.search-result .note { | |||
} | |||
.page-container { | |||
width: 900px; | |||
max-width: calc(100% - 220px); | |||
min-width: 320px; | |||
padding-left: 16px; |