Files
weave-scope/client/app/scripts/components/matched-text.js
David Kaltschmidt 749571ebe9 Review feedback
* 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
2016-05-11 18:08:59 +02:00

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);