mirror of
https://github.com/weaveworks/scope.git
synced 2026-03-03 18:20:27 +00:00
* Fix node-details-test for search * Label spacing and matched text truncation * Delete pinned search on backspace, add hint for metrics, escape % in URL * Fix text-bg on node highlight * Added tests for search-utils * Fix matching of other topologies, added comment re quick clear * s/cx/classnames/ * Ignore MoC keys when search in focus, blur on Esc * Fixes search term highlighting on-hover * Fix SVG exports * Fine-tuned search item rendering * Fixed search highlighting in the details panel * Dont throb node on hover * Hotkey for search: '/' * Keep focus on search when tabbing away from the browser * bring hovered node to top * background for search results on hover * fixed height for foreign object to prevent layout glitches * Dont blur focused nodes on search * More robust metric matchers * More meaningful search hints
109 lines
3.0 KiB
JavaScript
109 lines
3.0 KiB
JavaScript
import React from 'react';
|
|
import { connect } from 'react-redux';
|
|
|
|
const TRUNCATE_CONTEXT = 6;
|
|
const TRUNCATE_ELLIPSIS = '…';
|
|
|
|
/**
|
|
* Returns an array with chunks that cover the whole text via {start, length}
|
|
* objects.
|
|
*
|
|
* `('text', {start: 2, length: 1}) => [{text: 'te'}, {text: 'x', match: true}, {text: 't'}]`
|
|
*/
|
|
function chunkText(text, { start, length }) {
|
|
if (text && !isNaN(start) && !isNaN(length)) {
|
|
const chunks = [];
|
|
// text chunk before match
|
|
if (start > 0) {
|
|
chunks.push({text: text.substr(0, start)});
|
|
}
|
|
// matching chunk
|
|
chunks.push({match: true, text: text.substr(start, length)});
|
|
// text after match
|
|
const remaining = start + length;
|
|
if (remaining < text.length) {
|
|
chunks.push({text: text.substr(remaining)});
|
|
}
|
|
return chunks;
|
|
}
|
|
return [{ text }];
|
|
}
|
|
|
|
/**
|
|
* Truncates chunks with ellipsis
|
|
*
|
|
* First chunk is truncated from left, second chunk (match) is truncated in the
|
|
* middle, last chunk is truncated at the end, e.g.
|
|
* `[{text: "...cation is a "}, {text: "useful...or not"}, {text: "tool..."}]`
|
|
*/
|
|
function truncateChunks(chunks, text, maxLength) {
|
|
if (chunks && chunks.length === 3 && maxLength && text && text.length > maxLength) {
|
|
const res = chunks.map(c => Object.assign({}, c));
|
|
let needToCut = text.length - maxLength;
|
|
// trucate end
|
|
const end = res[2];
|
|
if (end.text.length > TRUNCATE_CONTEXT) {
|
|
needToCut -= end.text.length - TRUNCATE_CONTEXT;
|
|
end.text = `${end.text.substr(0, TRUNCATE_CONTEXT)}${TRUNCATE_ELLIPSIS}`;
|
|
}
|
|
|
|
if (needToCut) {
|
|
// truncate front
|
|
const start = res[0];
|
|
if (start.text.length > TRUNCATE_CONTEXT) {
|
|
needToCut -= start.text.length - TRUNCATE_CONTEXT;
|
|
start.text = `${TRUNCATE_ELLIPSIS}`
|
|
+ `${start.text.substr(start.text.length - TRUNCATE_CONTEXT)}`;
|
|
}
|
|
}
|
|
|
|
if (needToCut) {
|
|
// truncate match
|
|
const middle = res[1];
|
|
if (middle.text.length > 2 * TRUNCATE_CONTEXT) {
|
|
middle.text = `${middle.text.substr(0, TRUNCATE_CONTEXT)}`
|
|
+ `${TRUNCATE_ELLIPSIS}`
|
|
+ `${middle.text.substr(middle.text.length - TRUNCATE_CONTEXT)}`;
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
return chunks;
|
|
}
|
|
|
|
/**
|
|
* Renders text with highlighted search match.
|
|
*
|
|
* A match object is of shape `{text, label, match}`.
|
|
* `match` is a text match object of shape `{start, length}`
|
|
* that delimit text matches in `text`. `label` shows the origin of the text.
|
|
*/
|
|
class MatchedText extends React.Component {
|
|
|
|
render() {
|
|
const { match, text, maxLength } = this.props;
|
|
|
|
if (!match) {
|
|
return <span>{text}</span>;
|
|
}
|
|
|
|
return (
|
|
<span className="matched-text" title={text}>
|
|
{truncateChunks(chunkText(text, match), text, maxLength).map((chunk, index) => {
|
|
if (chunk.match) {
|
|
return (
|
|
<span className="match" key={index}>
|
|
{chunk.text}
|
|
</span>
|
|
);
|
|
}
|
|
return chunk.text;
|
|
})}
|
|
</span>
|
|
);
|
|
}
|
|
}
|
|
|
|
export default connect()(MatchedText);
|