I have a task to create API using ExpressJS that will manage highlights which will be made on the frontend. How can I keep track of my highlighted text if someone updates a part of the text?
I was keeping the three starting and ending characters of the highlighted text. But a problem is, how will I manage those characters if the text is edited.
const { textH, allText } = req.body;
let chars = { };
const enclosingChars = (hLighted, theString) => {
let startingChars = null, endingChars = null;
const index = theString.indexOf(hLighted);
const last3 = index + hLighted.length;
if ((index - 3) > -1) startingChars = theString.substring(index - 3, index);
if ((index + 3) <= theString.length) endingChars = theString.substring(last3, last3 + 3);
return { startingChars, endingChars };
};
if (allText.includes(textH)) {
chars = enclosingChars(textH, allText);
}
chars.hLighted = textH;
If a part of the highlighted text is edited, I will delete the highlighted in my storage. If not, I want to check if my starting and ending characters have changed, then I change them accordingly.
But I don't know how to get that highlighted text if its index changed
so it will be not as easy as you think if you consider keeping track of edits outside of the highlighted text.
the text might contain more than one similar phrase/word (to the highlighted)
the newly inserted phrase might be similar to the highlighted one (worse if before the main one in terms of indices)
so if any of the above scenarios happens the indices will be of no use because you can fall to the wrong text
Do the following
a. when highlighting keep the following
1. the content : to make sure if the edit happens in there, you know it and you can act accordingly
2. keep track of the index range of your text even in case the shift is required (when the text before it is edited)
3. take a screenshot of the entire article and store it so that when an edit to the article happens you know exactly which part is edited. this will require to check word by word and see if any word in the new text is different from the word of the same index in the old (screenshot). and you can shift the ranges of the highlighted text accordingly.
Remember any edit that happens after the highlighted text is innocuous.
Hope this helps.
Related
I am making an autocomplete drop-down list for my Pokemon website, and while the autocompleting drop-down bit works, it's not displaying the HTML entities properly. When I set the input's value to the entity, it simply prints out the text (e.g. ↑ and not a literal up arrow). I'm not sure what I am doing wrong, but here is a simplified version of it:
// There are usually a lot more, but just for simplification only one is shown
const natures = [
{name: "Lonely (Atk ↑ Def↓)"}
]
const natureInput = document.querySelector(".natureInput");
const suggestionsPanelNature = document.querySelector(".natureSuggestions");
// This is just used to see whether they have chosen one of the options
let set_nature = 0;
natureInput.addEventListener('keyup', function() {
set_nature = 0;
const input = natureInput.value;
suggestionsPanelNature.innerHTML = '';
// Find all of the natures that start with the input
const suggestions = natures.filter(function(nature) {
return nature.name.toLowerCase().startsWith(input.toLowerCase());
});
suggestions.forEach(function(suggested) {
// Display all matching natures
const div = document.createElement("div");
div.onclick = function() {
set_nature = 1;
natureInput.value = suggested.name; // This is the line that seems to be causing issues
suggestionsPanelNature.innerHTML = '';
};
div.classList.add("suggestion");
div.innerHTML = suggested.name; // This line, however, works fine
suggestionsPanelNature.appendChild(div);
});
if (input === '') {
suggestionsPanelNature.innerHTML = '';
}
})
So if someone clicked the Lonely (Atk↑ Def↓) option, it would come up in the input box as Lonely (Atk↑ Def↓), which is not what I want.
If you need anymore information please ask, but otherwise thanks in advance.
Im assuming you have an HTML similar to
<input type="text" class="natureInput">
<div class="natureSuggestions"></div>
If that's the case, you just need to replace ↑ and ↓ with ↑ and ↓. The input element is just normal text, so no need to escape it (Not when the user is typing, nor when you change it via js)
Fiddle: https://jsfiddle.net/ng6er7ov/2/
(Tip; is always nice to post a complete example with html and js somewhere like jsfiddle so people can just see what you have, instead of having to guess based on the code)
Currently you are just setting the innerHTML to exactly the string you have in natures.name. HTML only escapes certain characters like & and < so you can add the ↑ character directly into your string.
I've been working on creating an HTML parser and formatter, and I've just added a feature to optionally render whitespace visible, by replacing spaces with · (middle dot) characters, adding arrows for tabs and newlines, etc.
The full in-progress source code is here: https://github.com/kshetline/html-parser, and the most relevant file that's doing the CSS styling of HTML is here: https://github.com/kshetline/html-parser/blob/master/src/stylizer.ts.
While it's nice to be able to visualize whitespace when you want to, you wouldn't want spaces to be turned into middle dot characters if you select and copy the text. But that's just what happens, at least without some intervention.
I have found a crude way to fix the problem with a bit of JavaScript, which I've put into a Code Pen here: https://codepen.io/kshetline/pen/NWKYZJg.
document.body.addEventListener('copy', (event) => {
let selection = document.getSelection().toString();
selection = selection.replace(/·|↵\n|↵/g, ch => ch === '·' ? ' ' : '\n');
event.clipboardData.setData('text/plain', selection);
event.preventDefault();
});
I'm wondering, however, if there's a better way to do this.
My first choice would be something that didn't rely on JavaScript at all, like if there's some way via CSS or perhaps some accessibility-related HTML attribute that would essentially say, "this is the real text that should be copied, not what you see on the screen".
My second choice would be if someone can point me to more detailed documentation of the JavaScript clipboard feature than I've been able to find, because if I have to rely on JavaScript, I'd at least like my JavaScript to be smarter. The quick-and-dirty solution turns every middle dot character into a space, even if it was truly supposed to be a middle dot in the first place.
Is there enough info in the clipboard object to figure out which of the selected text has what CSS styling, so I could know to convert only the text that's inside <span>s which have my whitespace class, and still also find the rest of the non-whitespace text, in proper order, to piece it all back together again?
I still couldn't find much documentation on how selection objects work, but I played around with them in the web console, and eventually figured out enough to get by.
This is the JavaScript I came up with:
function restoreWhitespaceStrict(s) {
return s.replace(/·|[\u2400-\u241F]|\S/g, ch => ch === '·' ? ' ' :
ch.charCodeAt(0) >= 0x2400 ? String.fromCharCode(ch.charCodeAt(0) - 0x2400) : '');
}
const wsReplacements = {
'·': ' ',
'→\t': '\t',
'↵\n': '\n',
'␍\r': '\r',
'␍↵\r\n': '\r\n'
}
function restoreWhitespace(s) {
return s.replace(/·|→\t|↵\n|␍\r|␍↵\r\n|→|↵|␍|[\u2400-\u241F]/g, ws =>
wsReplacements[ws] || (ws.charCodeAt(0) >= 0x2400 ? String.fromCharCode(ws.charCodeAt(0) - 0x2400) : ''));
}
document.body.addEventListener('copy', (event) => {
const selection = document.getSelection();
let newSelection;
let copied = false;
if (selection.anchorNode && selection.getRangeAt) {
try {
const nodes = selection.getRangeAt(0).cloneContents().childNodes;
let parts = [];
// nodes isn't a "real" array - no forEach!
for (let i = 0; i < nodes.length; ++i) {
const node = nodes[i];
if (node.classList && node.classList.contains('whitespace'))
parts.push(restoreWhitespaceStrict(node.innerText));
else if (node.localName === 'span')
parts.push(node.innerText);
else
parts.push(node.nodeValue);
}
newSelection = parts.join('');
copied = true;
}
catch (err) {}
}
if (!copied)
newSelection = restoreWhitespace(selection.toString());
event.clipboardData.setData('text/plain', newSelection);
event.preventDefault();
});
I've tried this on three browsers (Chrome, Firefox, and Safari), and it's working on all of them, but I still took the precaution of both testing for the presence of some of the expected object properties, and then also using try/catch, just in case I hit an incompatible browser, in which case the not-so-smart version of fixing the clipboard takes over.
It looks like the selection is handled as a list of regular DOM nodes. Chrome's selection object has both an anchorNode and an extentNode to mark the start and end of the selection, but Firefox only has the anchorNode (I didn't check Safari for extentNode). I couldn't find any way to get the full list of nodes directly, however. I could only get the full list using the cloneContents() method. The first and last nodes obtained this way are altered from the original start and end nodes by being limited to the portion of the text content that was selected in each node.
I'm designing a rudimentary spell checker of sorts. Suppose I have a div with the following content:
<div>This is some text with xyz and other text</div>
My spell checker correctly identifies the div (returning a jQuery object entitled current_object) and an index for the word (in the case of the example, 5 (due to starting at zero)).
What I need to do now, is surround this word with a span e.g.
<span class="spelling-error">xyz</span>
Leaving me with the final structure like this:
<div>
This is some text with
<span class="spelling-error">xyz</span>
and other text
</div>
However, I need to do this without altering the existing user selection / moving the caret / invoking methods that do so e.g.
window.getSelection().getRangeAt(0).cloneRange().surroundContents();
In other words, if the user is working on the 4th div in the contenteditable document, my code would identify issues in the other divs (1st - 3rd) while not removing focus from the 4th div.
Many thanks!
You've tagged this post as jQuery but I don't think it's particularly necessary to use it. I've written you an example.
https://jsfiddle.net/so0jrj2b/2/
// Redefine the innerHTML for our spellcheck target
spellcheck.innerHTML = (function(text)
{
// We're using an IIFE here to keep namespaces tidy.
// words is each word in the sentence split apart by text
var words = text.split(" ");
// newWords is our array of words after spellchecking.
var newWords = new Array;
// Loop through the sentences.
for (var i = 0; i < words.length; ++i)
{
// Pull the word from our array.
var word = words[i];
if (i === 5) // spellcheck logic here.
{
// Push this content to the array.
newWords.push("<span class=\"mistake\">" + word + "</span>");
}
else
{
// Push the word back to the array.
newWords.push(word);
}
}
// Return the rejoined text block.
return newWords.join(" ");
})(spellcheck.innerHTML);
Worth noting my usage of an IIFE her can be easily reproduced by moving that logic to its own function declaration to make better use of it.
Be aware you also need to account for punctuation in your spellchecking instances.
I'm having a trouble when i try to force the user to enter data to a text input. I need to do something like the IP Address input in Windows.
I want to split the text input by dashes having something like this
10 - 10 - 10 - 10
Is there any way to do this ?
http://jsfiddle.net/87dug9oa/1/
function check(text) {
var result = [];
text = text.replace(/[^\d]/g,"");
while (text.length >= 3) {
result.push(text.substring(0, 3));
text = text.substring(3);
}
if(text.length > 0) result.push(text);
$("#ip").val(result.join("-"));
}
$("#ip").on("keyup", function() {
check($(this).val());
});
This creates a function, which adds dashes once 3 characters has been added (and the fourth is written).
Now, this does do what you want, but you need to add some additional stuff, such as checking for length and making the remove part work (because when you press any key, it will change the input's value, which will make the caret move to the last character).
Oh I almost forgot. This can be changed to the length of your choice, of course. Just change the "3" to be something else.
i'm trying to live edit a text box value so that the result will be split every two character,
adding a column and starting from some default character.
what i have till now is this code, that obviously doesn't work:
$('#textboxtext').keyup(function (){
var text = $("#textboxtext").val();
//$(text).attr('maxlength', '12');
var splitted = text.match(/.{2}|.{1,2}/g);
var result = ("B8:27:EB:" + splitted.join(':'));
});
i need the live split and the default character inside the textbox but i really don't know where to start...
From your code, it seems like you're trying to create a text box that has some very specific behavior. It looks like it needs to format its value in such a way that it always begins with certain 'prefix' of B8:27:EB:, and every subsequent pair of characters is is separated by a :. This is actually a very complex behavior and you have to consider a number of different interactions (e.g. what happens when the user attempts to delete or modify the prefix). I usually try to avoid such complex controls if possible, however here is a quick implementation:
$('#textboxtext').keyup(function (e){
var prefix = "B8:27:EB:",
text = $(this).val(),
splitted, result;
if (text.indexOf(prefix) == 0)
text = text.substr(9);
else if (prefix.indexOf(text) == 0)
text = "";
text = text.replace(/:/g, '');
splitted = text.match(/.{1,2}/g) || [];
result = prefix + splitted.join(':');
$(this).val(result);
});
Demonstration
Type inside the text box and see what happens. Also note, there are all kinds of interaction that this implementation doesn't account for (e.g. right-clicking and pasting into the text box), but it's a start.