I am attempting to add a search feature to Quill and want to highlight the text that it finds. I am having trouble getting the range index for the text it finds, and it is likely that I am not using the correct approach.
So far, I get the text using getContents() and can find matches by iterating through the lines. However, I have not been able to find the correct index position for setSelection(). A second issue is that I want the window to scroll to where the selection is found, and it is not scrolling into view.
...
myKeyPhrases = ['obvious', 'orange', 'apple'];
var myDelta = editor.getContents();
myDelta.eachLine((line, attributes, i) => {
mytext = line.filter((op) => typeof op.insert === 'string').map((op) => op.insert).join('');
mytext = mytext.toLowerCase();
ndx = isKeyPhraseFound(mytext, myKeyPhrases);
if (ndx >= 0){
// The code finds the matches OK to here.
// The next 4 lines don't get the correct range and scroll.
index = i;
editor.focus();
editor.setSelection(index, 1, Quill.sources.USER);
editor.scrollIntoView();
return index;
}
});
My desired results would be that a found text match would be selected and that the window would be scrolled so that the selection is displayed. The actual results are that the wrong text is selected and the window is not scrolled to view the selection.
You're basically there. I suspect your issue is that you're filtering out block elements, which acts similarly to Quill's getText method. According to the docs:
Non-string content are omitted, so the returned string’s length may be shorter than the editor’s as returned by getLength.
This is because non-text elements usually have a length of 1, so for every one of those you omit, your index will drift by 1.
A simple workaround for this is to replace any non-text elements with a newline character, which has length 1, and the added benefit of causing a search across it to fail (unless you let users search with newlines - maybe then you can pick another special character like a control character).
The following method should return a plain-text representation of your Quill contents whose length matches quill.getLength, and therefore should be able to be searched using whatever JavaScript text search method you want (eg indexOf for simple searches).
function textContents() {
return quill.getContents().ops
.reduce((text, op) => {
if (typeof op.insert === 'string') {
// If the op is a string insertion, just concat
return text + op.insert;
} else {
// Otherwise it's a block. Represent this as a newline,
// which will preserve the length of 1, and also prevent
// searches matching across the block
return text + '\n';
}
}, '');
}
Here's a rough example: https://codepen.io/alecgibson/pen/GLVzPb
Related
Hi recently I was writing a code which basically askes users to input a cost for any item.
The item name or price are not important so far.
How the system works is that a prompt pops up and asks what is the cost of the item, and when you enter a cost, it asks you again (while loop). When total cost reaches $36, while loop ends and an alert screen pops up saying your shipping is free since it is over $35.
My problem is when I enter string value as a user input it takes the value into cost array and since it goes as Nan value, my cost calculation never end even though it passes $36.
How can I eliminate any string input and do not push it into the array?
Here is my code:
while (complete === false) {
var costValue = prompt('Enter the cost of the item?');
//If user click cancel
if (costValue === null) {
alert('Process canceled');
complete = true;}
//AT THIS LINE HOW CAN I ELIMINATE STRING VALUES
//convert input to a number
userInput = parseInt(costValue);
/push the value into the array
myCost.push(userInput);
Leading special character will give you NAN and if you use a special character after the Int value then the parseInt will give you the correct result. You can also use regex to remove all the special characters.
userInput.replace(/[^\w\s]/gi, '')
You can read about parseInt from here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt
After you parse your user input into an integer (i.e. using parseInt method), then have an if statement that checks whether the parsed values is not NaN. If so, then only push the parsed value into your array.
// TRY parse user input into an integer
userInput = parseInt(costValue);
// only push into your array when it is not `NaN`
if (userInput !== 'NaN') {
// push the value into the array
myCost.push(userInput);
}
Try this code, it should work
var userInput;
var totalInput=0;
var complete = false;
var costValue;
var numPattern = /^\d+$/;
while (!complete) {
costValue = prompt('Enter the cost of the item?');
//If user click cancel
if (costValue === null)
{
alert('Process canceled');
complete = true;
}
//convert input to a number if it matches regular expression
if (costValue.match(numPattern)){
userInput = parseInt(costValue);
totalInput+=userInput;
if (totalInput>35) {
complete=true;
alert("Cost is $" + totalInput + ". Shipping is free over $35");
}
}
}
On the other hand, if you want to go another route, you can check for NaN using this function isNaN(userInput) and exclude it from your calculations
I have a .txt with content inserted in textBox1 and I already show the content to textBox2.
I want to move the part else { a = c; } to another textBox3 and delete this part in textBox2. How can I do it?
The content of text:
a = Calculation();
all = a;
if (a == 0)
{
a = b;
}
**else
{
a = c;
}**
all = b;
all = c;```
In modern programming, there is a tendency to separate the data (=model) from how this data is displayed (=view). This enables changing how you display the data without having to change the model. You can also change the model, without having to change the view.
For this separation, we need adapter code that couples the Model to the View: quite often called the ViewModel. The abbreviation for this idea is MVVM. Google this if you want to k know more about the theory behind this.
"I have a .txt with content"
First of all: you form is not interested where your data comes from, hide this in your model.
string GetText() {... // Read from file }
If later you decide that text should be read from a different source, for instance from a database, or from the internet, or maybe you decide that you ask the operator to type the text, the rest of your model and view won't have to change.
Besides: it will be easier to unit test this part.
"inserted in textBox1 and I already show the content to textBox2. I want to move the part else { a = c; } to another textBox3 and delete this part in textBox2"
I think you mean to say, that from the text that you fetched in GetText(), you want to show the "else" part (TODO: define the else part), in textBox3, and show the complete text without the "else" part in textBox2. The complete text should be "inserted" in textBox1.
So we need a method to separate your test into two: the "Else" part and the rest. Furthermore we need procedures to "Insert" the complete text, the "Else" text and the "Rest" text:
void InsertText (string text)
{
// not sure what "Insert" means. Do you mean replace?
// or add to the current cursor position?
this.textBox1.Text = ...
}
void ShowElsePart(string elseText)
{
this.textBox3.Text = elseText;
}
void ShowRest(string restText)
{
this.textBox2.Text = restText;
}
So I've hidden how I view "Else" part, the "Rest" part and what I mean with "Insert text". If later you decide you don't want to show it in a text Box, but for instance in a RichTextBox, or in a Listview, only one procedure needs to change.
We need to divide the text into the "Else" part and the "Rest".
You defined the "Else" part as the part else { a = c; }, so without the ** and more important: without new lines, or was this just the description, and could 'else { a = c; }be different text:else a = c;, or else { x = y; }or evenelse { while (x) {...}`?
This uncertainty is a good example of why you should separate the model from the view: if you decide to change your definition of the "Else" part, outsiders don't have to change.
void DivideText(string input, our string elsePart, out string rest)
{
// TODO implement
}
First we'll put it all together, then we'll do the implementation:
void DisplayInput()
{
string text = this.GetText();
this.InsertText(text);
this.DivideText(text, out string elseText, out string restText);
this.ShowElsePart(elseText);
this.ShowRest(restText);
}
Apart from that it is fairly easy to understand what happens, this code is highly reusable, easy to change and easy to text. If you want to read your text from a database, instead of plain text file, only one procedure needs to change. If you don't want to insert the text in textBox1, but in a file: only one place to change. If you define "Else" part differently: again only place to change.
Now about the "Else" part. According to your definition, "Else" is fairly simple:
const string elsePart = "else { a = c; }"
int elseStartIndex = text.IndexOf(elsePart, 0);
if (elseStartIndex < 0)
{
// elsePart not found
string restText = text;
}
else
{
string restText = text.Remove(elseStartIndex, elsePart.Length;
}
It could be that you meant something else with "Else" part: everything from the first else until the first }.
int indexStartElse = text.IndexOf("else", 0);
// TODO: what if no else?
int indexEndElse = text.IndexOf("}", indexStartElse);
// TODO: what if no }?
int elseTextLength = indexEndElse - indexStartElse;
string elseText = text.SubString(indexStartElse, elseTextLength);
string restText = text.Remove(indexStartElse, elseTextLength);
I am trying to set up a search system for a database where each element (a code) in one table has tags mapped by a Many to many relationship. I am trying to write a controller, "search" where I can search a set of tags which basically act like key words, giving me an element list where the elements all have the specified tags. My current function is incredibly naive, basically it consists of retrieving all the codes which are mapped to be a tag, then adding those a set, then sorting the codes by how many times the tags for each code is found in the query string.
public List<Code> naiveSearch(String queryText) {
String[] tagMatchers = queryText.split(" ");
Set<Code> retained = new HashSet<>();
for (int i = 0; i < Math.min(tagMatchers.length, 4); i++) {
tagRepository.findAllByValueContaining(tagMatchers[i]).ifPresent((tags) -> {
tags.forEach(tag -> {
retained.addAll(tag.getCodes());
}
);
});
}
SortedMap<Integer, List<Code>> matches = new TreeMap<>();
List<Code> c;
for (Code code : retained) {
int sum = 0;
for (String tagMatcher : tagMatchers) {
for (Tag tag : code.getTags()) {
if (tag.getValue().contains(tagMatcher)) {
sum += 1;
}
}
}
c = matches.getOrDefault(sum, new ArrayList<>());
c.add(code);
matches.put(sum, c);
}
c = new ArrayList<>();
matches.values().forEach(c::addAll);
Collections.reverse(c);
return c;
}
This is quite slow and the overhead is unacceptable. My previous trick was a basically retrieval on the description for each code in the CRUDrepository
public interface CodeRepository extends CrudRepository<Code, Long> {
Optional<Code> findByCode(String codeId);
Optional<Iterable<Code>> findAllByDescriptionContaining(String query);
}
However this is brittle since the order of tags in containing factors into whether the result will be found. eg. I want "tall ... dog" == "dog ... tall"
So okay, I'm back several days later with how I actually solved this problem. I used hibernate's built in search library which has a very easy implementation in spring. Just paste the required maven coordinates in your POM.xml and it was ready to roll.
First I removed the manytomany for the tags<->codes and just concatenated all my tags into a string field. Next I added #Field to the tags field and then wrote a basic search Method. The method I wrote was a very simple search function which took a set of "key words" or tags then performed a boolean search based on fuzzy terms for the the indexed tags for each code. So far it is pretty good. My database is fairly small (100k) so I'm not sure about how this will scale, but currently each search returns in about 20-50 ms which is fast enough for my purposes.
I need to do an assertion to check if an element is present inside 'elements.all'.
My first idea is to run a for loop and put an expect inside. Not a great idea because it is checking every single items on list. So if I have 30 items, I might end up with 29 fails.
element.all(by.css(element)).then(function(itemList) {
console.log("Total values in dropdown are: " + itemList.length);
for (i = 0; i < itemList.length; i++) {
itemList[i].getText().then(function(text) {
console.log(text);
expect(text).toEqual('whatever-i-need-to-assert');
});
};
});
In order to solve this problem, I nest an IF statement that will 'pre-check' for a string match. Another bad idea because if there is no match, my expect will never run, thus, giving me a false pass:
element.all(by.css(".item a.gc-exercises-link")).then(function(itemList) {
console.log("Total values in dropdown are: " + itemList.length);
for (i = 0; i < itemList.length; i++) {
itemList[i].getText().then(function(text) {
console.log(text);
if (text == 'BOATLIFT-EXERCISE') {
console.log('Match');
expect(text).toEqual('BOATLIFT-EXERCISE');
} else {
console.log('No Match');
};
});
};
});
Clearly, I am in the wrong path here. Can someone give me an idea how to properly expect for a 'Text' when using element.all. I just need to prove that a text is present on the said list.
Thanks!
Here is an example to check that there is a link with the text "Terms" in a page :
browser.get('https://www.google.co.uk/');
expect(element.all(by.css('a')).getText()).toContain('Terms');
Note that for each element, protractor needs to interogate the browser, which could be slow especially if there is a lot of elements.
A quicker solution would be to check that at least one element is present with a XPath including the expected text:
browser.get('https://www.google.co.uk/');
expect(element.all(by.xpath("//a[text()='Terms']")).count()).toBeGreaterThan(0);
If you just want to check it's present (and other list item's won't interfere), you could call .getText() on the array after element.all, before .then and use toContain()
element.all(by.css(".item a.gc-exercises-link")).getText().then(function(itemList) {
expect(itemList).toContain('some text');
};
Or if you know the index:
element.all(by.css(".item a.gc-exercises-link")).getText().then(function(itemList) {
expect(itemList[3]).toEqual('some text');
}
As a side note: you can use .each() instead of creating a for loop https://angular.github.io/protractor/#/api?view=ElementArrayFinder.prototype.each
You can use filter function.
$$("span").filter(function(elem,index){
return elem.getText().then(function(txt){
return txt === 'text to compare';
});
}).then(function(eleList){ // you get the list of elements containing the text here
console.log(eleList.length);
});
I'm writing a flexible search algorithm for a portion of my XML file. I have this exact same method working flawlessly elsewhere, thus why this is driving me batty.
Here is the section of my function that I'm having trouble with.
var searchResults:Array = [];
var r:Array = [];
//Other code...
var classSplit:Array = inClass.split("-");
var profs:Array = getCourseProfs(classSplit[0], classSplit[1]);
trace(searchResults + "-vs-" + profs);
for each(var si:String in searchResults)
{
trace(si + " is at index " + profs.indexOf(si));
trace(profs);
if(r.indexOf(si) == -1 && profs.indexOf(si) != -1)
{
r.push(si);
trace(r);
}
}
And here is the output for a particular run's trace statements (from the above code).
GABIOMA1,GABIOMA1-vs-GABIOMA1,MITCHKA1,GIBSOCA1
GABIOMA1 is at index -1
GABIOMA1,MITCHKA1,GIBSOCA1
GABIOMA1 is at index -1
GABIOMA1,MITCHKA1,GIBSOCA1
Final:
As you can see, the function has two results in the searchResults array (of strings). They're both identical since I haven't yet sorted out duplicate search results. "GABIOMA1,GABIOMA1"
Then, I'm comparing that to an array "profs" with these three values: "GABIOMA1,MITCHKA1,GIBSOCA1"
You can see the matching result in index 0 of profs. However, as you see from the next line, profs.indexOf(si)' whensi` is "GABIOMA1" incorrectly returns "-1".
I can write a workaround in a heartbeat, but I need to know why indexOf has decided to bork.
EDIT: Even more infurating, the code in the other branch of the IF statement from this works like a charm...
for each(var s:String in searchResults)
{
if(r.indexOf(s) == -1)
{
r.push(s);
}
}
EDIT 2: This is my WORKING workaround. As you can see, all of the arrays are working properly.
for each(var si:String in searchResults)
{
var match:Boolean = false;
for each(var pi:String in profs)
{
if(si == pi)
{
pr.push(si);
break;
}
}
}
Given your trace(profs) returns a single string, your array contains a string GABIOMA1,MITCHKA1,GIBSOCA1 as a single entity, that's why your index of GABIOMA1 returns -1. You need to do split by comma within getCourseProfs() to get your GABIOMA1 as a separate string.
UPDATE: I have tested with the following:
var t:Array = ['GABIOMA1', 'MISCHKA1', 'GIBSOCA1'];
trace(t);
trace(t.indexOf('GABIOMA1'));
var a:Array = ['MITCHKA1', 'GABIOMA1\x00'];
for each(var si:String in a) trace(si, 'is at index', t.indexOf(si));
This gave me an expected result of -1 for either element of a, and expected trace results as in your example, however, equality test fails. Please try trimming the searchResults strings prior to checking for indexOf().
A side note: placing a \x00 as the first char of a string produces different results for tracing! trace(si, 'is at index', t.indexOf(si)); results in an empty string displayed in place of si like this: is at index -1, while trace(si + ' is at index ' + t.indexOf(si)); results in full string displayed: GABIOMA1 is at index -1. So carefully check either string for non-printable symbols, it's likely there is some hidden zero char that screws indexOf().