Quick background - I have dyslexia and it can be challenging sometimes looking at certain phrases or numbers to not mix things up, so I've been told to use colour to fix this. Full disclosure, I am not a programmer, but I spoke to one of the developers at work, and they said that they can probably hack something together for me to help out if I can provide them with some base Javascript code to work off.
Is someone able to assist? I have no idea what I'm looking for or what to search for. I found this, but I think it needs to be more complex.
Basically, I want letters to be one colour, symbols to be another colour, numbers a third colour, then my "bad" characters highlighted in something else.
Bad Characters
E / 3 = Red / Orange
L / I = Red / Orange
Other characters
A - Z = Black
1 - 9 = Blue
! - ) = Purple
I hope this makes sense. Feel free to ask me any questions.
Thank you sincerely.
Update: To clarify, there is a box where passwords are generated and I need to transcribe that password into an application that does not accept copy/paste. It is a single phrase/area with no hyperlinks at all.
You do realise what you want is not straight forward!
What if you have a hyperlink inside a paragraph? What if the HTML is malformed?
You said your developer needs something to start with so the below will hopefully give him a base to work from.
Basically we grab all the likely candidates for text nodes (headings and a paragraph, you can extend this to other items).
Then we loop all candidates and check each character. We check if the character compares to anything from our special character list (var all). If they don't we just add them to a string to replace later.
If they do match any one of our special lists we then wrap them in a span with the relevant colour using wrapSpan function.
Once we have checked the whole string for a candidate we then replace the inner HTML for that candidate.
**There is a LOT of work you would have to do with this to make it functional due to the complexities of HTML and the below is very inefficient, but it should give you an idea of where to start.
var candidates = 'h1,h2,h3,h4,h5,h6,p';
var candidateContainers = document.querySelectorAll(candidates);
var red = 'EL';
var ora = '3I'
var blu = '124567890';
var pur = '!';
var all = red + ora + blu + pur;
var char;
candidateContainers.forEach(function(entry) {
var str = entry.innerHTML;
var newStr = "";
for (var i = 0; i < str.length; i++) {
char = str.charAt(i);
if(all.indexOf(char) == -1){
// console.log("do nothing", char);
newStr += char;
}
if(red.indexOf(char) > -1){
newStr += wrapSpan('red', char);
}
if(ora.indexOf(char) > -1){
newStr += wrapSpan('orange', char);
}
if(blu.indexOf(char) > -1){
newStr += wrapSpan('blue', char);
}
if(pur.indexOf(char) > -1){
newStr += wrapSpan('purple', char);
}
}
entry.innerHTML = newStr;
});
function wrapSpan(colour, char){
return '<span style="color: ' + colour + '">' + char + '</span>';
}
//console.log(candidateContainers);
<h1>THIS SENTENCE CONTAINS LETTERS THAT SHOULD BE HIGHLIGHTED SUCH AS E and 3!</h1>
<p>This sentence contains the numbers 1,2,3,4,5,6,7,8,9 and 3 should be a different colour, the exclamantion point should also be a different colour!</p>
<p>This is as simple as it gets as you can see if fails on this sentence due to an existing hyperlink</p>
Unfortunately there's no CSS selector to target specific chars which would have been useful in this case.
This is an interesting problem to understand people with dyslexia needs.
Some simple Javascript can make it like the following code. All you have to do is enclosing each character type with the correct marker.
Working with a configuration array containing your needs based on regular expression is the way to do. In the code below I used color keys for the object, but they are used as CSS identifier (which is not considered appropriate by everybody)
function color_format(ch) {
var config={
'red' : /[EL]/,
'orange': /[3l]/,
'black' : /[a-zA-Z]/,
'blue' : /[0-9]/,
// purple is the default color for non matching chars
};
res=cur='';
for (var i=0;i<ch.length;i++) {
match = "";
Object.keys(config).every(key => {
if (ch[i].match(config[key])) {
match += " " + key;
return false;
}
return true;
});
match=match.trim();
if (match != cur) {
if (cur) {
res += "</span>";
}
if (match) {
res+= "<span class='" + match + "'>";
}
cur=match;
}
res+=ch[i];
}
if (cur) res+='</span>';
return res;
}
document.getElementById("new-password").addEventListener("click",function(e){
// the two following lines are only used to generate a random password for the example
var s = "!(),;:.'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
password=Array.apply(null, Array(10)).map(function() { return s.charAt(Math.floor(Math.random() * s.length)); }).join('');
document.getElementById("password-container").innerHTML=color_format(password);
});
#password-container {
color: purple;
font-family: monospace;
font-size: 3em;
}
#password-container .black {
color: black;
}
#password-container .orange {
color: orange;
}
#password-container .blue {
color: blue;
}
#password-container .red {
color: red;
}
Your new password is : <div id="password-container">
</div>
<button id="new-password">
Generate password
</button>
Related
This is about a Chrome Extension.
Suppose a user select any text on a page, then clicks a button to save it. Via window.getSelection() I can get that text without the underlying html markup.
I store that text. For demo purposes, let's say the text is:
"John was much more likely to buy if he knew the price beforehand"
The next time the user visits the page, I want to find that text on the page. The issue is, the html for that text is actually:
<b>John was much more likely to buy if he knew the price <span class="italic">beforehand</span></b>
The second issue is that this system needs to work even if the selection is dirty, i.e. it starts/ends mid DOM node.
What I've build is bit of a fat solution, so I am curious how I can make it more efficient and/or smaller. This is the whole thing:
text.split("").map(function(el, i, arr){
if(specials.includes(el)){
return "\\"+el;
}
return el;
})
.join("(?:\\s*<[^>]+>\\s*)*\\s*");
where text is the saved text and specials is
var specials = [
'/', '.', '*', '+', '?', '|',
'(', ')', '[', ']', '{', '}', '\\'
];
The process is:
Split text into single characters
For each character, check if it's a special char and if so, prepend it with \
Join all letters together with regEx that check if there's any whitespace or html tags inbetween
My question is, can it be done in a better way? I get the "bruteforcing" feeling with this solution and I don't know if it would actually cause lag on larger sites/selection texts.
Plus, it doesn't work for SPAs where text may update a bit after the DOM is ready.
Thank you for any input.
EDIT:
So initially I was using mark.js, which doesn't handle this at all, but not 12 hours after I posted this question the maintainer release v8.0.0 that uses NodeList and handles my use case. The feature is "acrossElements", located here.
create a Range object
set it so that it spans the entire document from start to end
check if the string of interest is in its toString()
clone range twice
apply binary search by moving the start/end points of the subranges into roughly their midpoint. this can be approximated by finding the first descendant with > 1 child nodes and then splitting the child list
goto 3
this should roughly take n log m steps where n is the document text length and m the number of nodes.
Build the entire text representation of the document manually from each node with nodeType of Node.TEXT_NODE, saving the node reference and its text's start/end positions relative to the overall string in an array. Do it just once as DOM is slow, and you might want to search for multiple strings. Otherwise the other answer might be much faster (without actual benchmarks it's a moot point).
Apply HTML whitespace coalescing rules.
Otherwise you'll end up with huge spans of spaces and newline characters.
For example, Range.toString() doesn't strip them, meaning you'd have to convert your string to a RegExp with [\s\n\r]+ instead of spaces and all other special characters like {}()[]|^$*.?+ escaped.
Anyway, it'd be wise to use the converted RegExp on document.body.textContent before proceeding (easy to implement, many examples on the net, thus not included below).
A simplified implementation for plain-string search follows.
function TextMap(baseElement) {
this.baseElement = baseElement || document.body;
var textArray = [], textNodes = [], textLen = 0, collapseSpace = true;
var walker = document.createTreeWalker(this.baseElement, NodeFilter.SHOW_TEXT);
while (walker.nextNode()) {
var node = walker.currentNode;
var nodeText = node.textContent;
var parentName = node.parentNode.localName;
if (parentName==='noscript' || parentName==='script' || parentName==='style') {
continue;
}
if (parentName==='textarea' || parentName==='pre') {
nodeText = nodeText.replace(/^(\r\n|[\r\n])/, '');
collapseSpace = false;
} else {
nodeText = nodeText.replace(/^[\s\r\n]+/, collapseSpace ? '' : ' ')
.replace(/[\s\r\n]+$/, ' ');
collapseSpace = nodeText.endsWith(' ');
}
if (nodeText) {
var len = nodeText.length;
textArray.push(nodeText);
textNodes.push({
node: node,
start: textLen,
end: textLen + len - 1,
});
textLen += len;
}
}
this.text = textArray.join('');
this.nodeMap = textNodes;
}
TextMap.prototype.indexOf = function(str) {
var pos = this.text.indexOf(str);
if (pos < 0) {
return [];
}
var index1 = this.bisectLeft(pos);
var index2 = this.bisectRight(pos + str.length - 1, index1);
return this.nodeMap.slice(index1, index2 + 1)
.map(function(info) { return info.node });
}
TextMap.prototype.bisect =
TextMap.prototype.bisectLeft = function(pos) {
var a = 0, b = this.nodeMap.length - 1;
while (a < b - 1) {
var c = (a + b) / 2 |0;
if (this.nodeMap[c].start > pos) {
b = c;
} else {
a = c;
}
}
return this.nodeMap[b].start > pos ? a : b;
}
TextMap.prototype.bisectRight = function(pos, startIndex) {
var a = startIndex |0, b = this.nodeMap.length - 1;
while (a < b - 1) {
var c = (a + b) / 2 |0;
if (this.nodeMap[c].end > pos) {
b = c;
} else {
a = c;
}
}
return this.nodeMap[a].end >= pos ? a : b;
}
Usage:
var textNodes = new TextMap().indexOf('<span class="italic">');
When executed on this question's page:
[text, text, text, text, text, text]
Those are text nodes, so to access corresponding DOM elements use the standard .parentNode:
var textElements = textNodes.map(function(n) { return n.parentNode });
Array[6]
0: span.tag
1: span.pln
2: span.atn
3: span.pun
4: span.atv
5: span.tag
I'm using ng-repeat to fill a table. Some elements have a pretty long length, and I'm looking for a way to cut the content into multiple lines, if a specific length is reached.
During research I found Angulars limitTo, but it does not exactly look like I was looking for.
E.g. hi i'm a long description and oh, a package (org.foo.awesome.stuff) should convert into hi i'm a long description and oh,'
a package (org.foo.awesome.stuff)
Big thanks in advance.
Write a custom filter:
angular.module('filters', []).filter('lineBreaker', function() {
return function(input, breakLength) {
var newString = "";
for (var i = 0; i < input.length; i++) {
newString = newString+input[i];
if (i%breakLength == 0) {
newString = newString+"\n";
}
}
return newString;
};
});
Called like:
{{ expression | lineBreaker: 10 }}
I'm sure there is a more performant way to do this, but it will get the job done.
app.filter("break", function(){
return function(input, length){
return input.match(new RegExp(".{1," + length + "}", 'g')).join("<br/>");
}
});
You can use something like the following to insert a line break every limit characters:
var a = "hi i'm a long description and oh, a package (org.foo.awesome.stuff)";
var limit = 10;
for (var i = 0; i < a.length; i++) {
if (i % (limit + 1) === 0 && i > 0) {
a = [a.slice(0, i), '\n', a.slice(i)].join('');
}
}
console.log(a);
/** Output:
hi i'm a lo
ng descrip
tion and o
h, a packa
ge (org.fo
o.awesome.
stuff)"
*/
Throw that into a custom filter.
An easier solution would be to use CSS to modify the word-wrap of the text in the elements of a table.
e.g.
th {
word-wrap:normal;
}
td {
word-wrap:normal;
}
In addition to this, you would probably have to edit the width of your table elements so that word-wrapping will occur (instead of creating a box as long as your text). This can be simply achieved by editing the css styling as shown in the following code. There are two options (depending on if you want your webpage to be scaleable or not)
Option 1:
th {
width:300px;
}
...
Option 2:
td {
width:10%
}
...
Also noteable about this solution, it DOES NOT break the given string at any word (unless otherwise told to do so). You can read more about word-wrap here.
I want to be able to link any word of my choice to a specific URL for example:
I want the word "goat" to link to "http://goat.com" across the entire website. So all "goat"/s will link to that URL right across the website.
I am using wordpress and I have not yet found a plugin to do this. If I can get a solution to this I would most likely create a plugin for this functionality.
I know how to target one word on a single page. But I would like it to be across all the pages and all the words in those pages( I used JavaScript for this).
Something like this may work for you.
function replaceWithUri(textToReplace, element){
element.innerHTML = element.innerHTML.replace(textToReplace, '<a href="http://www.' + textToReplace + '.com" >' + textToReplace + '</a>');
}
replaceWithUri('goat', document.getElementsByTagName('body')[0]);
Here's a crappy solution but it's better than nothing:
I found some code here which searches for a world across the whole page so I copy pasted that and modified it.
The replaceWord variable cannot contain the same string as word, otherwise it'll loop infinitely.
var word = " goat",
replaceWord = " <a href = 'http://goat.com'>goat</a>",
queue = [document.body],
curr
;
while (curr = queue.pop()) {
if (!curr.textContent.match(word)) continue;
for (var i = 0; i < curr.childNodes.length; ++i) {
switch (curr.childNodes[i].nodeType) {
case Node.TEXT_NODE : // 3
if (curr.childNodes[i].textContent.match(word)) {
curr.innerHTML = curr.innerHTML.replace(word,replaceWord);
}
break;
case Node.ELEMENT_NODE : // 1
queue.push(curr.childNodes[i]);
break;
}
}
}
Hello goat
<div>Look a goat</div>
This might be a bit resource intensive and replaceWord cannot contain the same string as word, otherwise it'll loop forever.
document.onload = function() {
var word = " goat",
replaceWord = " <a href = 'http://goat.com'>goat</a>";
while(document.body.innerHTML.indexOf(word) !== -1) {
document.body.innerHTML = document.body.innerHTML.replace(word,replaceWord);
}
}
Hello goat
<div>Look a goat</div>
I'm programming my own autocomplete textbox control using C# and javascript on clientside. On client side i want to replace the characters in string which matching the characters the user was searching for to highlight it. For example if the user was searching for the characters 'bue' i want to replace this letters in the word 'marbuel' like so:
mar<span style="color:#81BEF7;font-weight:bold">bue</span>l
in order to give the matching part another color. This works pretty fine if i have 100-200 items in my autocomplete, but when it comes to 500 or more, it takes too mutch time.
The following code shows my method which does the logic for this:
HighlightTextPart: function (text, part) {
var currentPartIndex = 0;
var partLength = part.length;
var finalString = '';
var highlightPart = '';
var bFoundPart = false;
var bFoundPartHandled = false;
var charToAdd;
for (var i = 0; i < text.length; i++) {
var myChar = text[i];
charToAdd = null;
if (!bFoundPart) {
var myCharLower = myChar.toLowerCase();
var charToCompare = part[currentPartIndex].toLowerCase();
if (charToCompare == myCharLower) {
highlightPart += myChar;
if (currentPartIndex == partLength - 1)
bFoundPart = true;
currentPartIndex++;
}
else {
currentPartIndex = 0;
highlightPart = '';
charToAdd = myChar;
}
}
else
charToAdd = myChar;
if (bFoundPart && !bFoundPartHandled) {
finalString += '<span style="color:#81BEF7;font-weight:bold">' + highlightPart + '</span>';
bFoundPartHandled = true;
}
if (charToAdd != null)
finalString += charToAdd;
}
return finalString;
},
This method only highlight the first occurence of the matching part.
I use it as follows. Once the request is coming back from server i build an html UL list with the matching items by looping over each item and in each loop i call this method in order to highlight the matching part.
As i told for up to 100 items it woks pretty nice but it is too mutch for 500 or more.
Is there any way to make it faster? Maybe by using regex or some other technique?
I also thought about using "setTimeOut" to do it in a extra function or maybe do it only for the items, which currently are visible, because only a couple of items are visible while for the others you have to scroll.
Try limiting visible list size, so you are only showing 100 items at maximum for example. From a usability standpoint, perhaps even go down to only 20 items, so it would be even faster than that. Also consider using classes - see if it improves performance. So instead of
mar<span style="color:#81BEF7;font-weight:bold">bue</span>l
You will have this:
mar<span class="highlight">bue</span>l
String replacement in JavaScript is pretty easy with String.replace():
function linkify(s, part)
{
return s.replace(part, function(m) {
return '<span style="color:#81BEF7;font-weight:bold">' + htmlspecialchars(m) + '</span>';
});
}
function htmlspecialchars(txt)
{
return txt.replace('<', '<')
.replace('>', '>')
.replace('"', '"')
.replace('&', '&');
}
console.log(linkify('marbuel', 'bue'));
I fixed this problem by using regex instead of my method posted previous. I replace the string now with the following code:
return text.replace(new RegExp('(' + part + ')', 'gi'), "<span>$1</span>");
This is pretty fast. Much faster as the code above. 500 items in the autocomplete seems to be no problem. But can anybody explain, why this is so mutch faster as my method or doing it with string.replace without regex? I have no idea.
Thx!
How do I turn this text:
• Ban Ki-moon calls for immediate ceasefire• Residents targeted in
al-Qusayr, witnesses tell HRWIsrael ignoring expanding violence by
settlers, EU reports9.18am: Footage from activists suggests that
opposition forces continue to resist government troops.This footage...
into this text:
Ban Ki-moon calls for immediate ceasefire. Residents targeted in
al-Qusayr, witnesses tell HRW. Israel ignoring expanding violence by
settlers, EU reports. 9.18am: Footage from activists suggests that
opposition forces continue to resist government troops. This
footage...
This needs to be fixed with javascript (multiple .replace commands are possible)
"• " has to be removed and replaced by a ". ", however the first "• " should just be removed
If there is no space after a dot ".", a space must be added (.This footage)
If there is no space before a time (9.18am), a space must be added
If there is no space before a capital letter (HRWIsrael) that is
followed by non-capital letters, then a dot and space ". " must be added in front
of that non-capital letter.
Breaking down into several replace statements (as listed below) is the way I would go about it (working fiddle).
The fixBullets function will turn all bullets into HTML Entities and the fixBulletEntities fixes those. I did this to normalize bullets as I'm not sure if they are just bullet characters or HTML entities in your source string.
The fixTimes function changes "9.18am:" into " 9:18am. " (otherwise, the fixPeriods function makes it look like " 9. 18am" which I am sure you do not want.
One major caveat regarding the fixCapitalsEndSentence function... This will also convert strings like "WOrDS" into "WO. rDS" which may not be what you want.
At the least, this should get you started...
function fixBullets(text) {
var bullets = /•/g;
return text.replace(bullets, '•');
}
function fixBulletEntities(text) {
var bulletEntities = /•/ig;
text = text.replace(bulletEntities, '. ');
if (text.indexOf('. ') === 0) {
text = text.substring(2);
}
return text;
}
function fixTimes(text) {
var times = /(\d+)[\.:](\d+[ap]m):?/ig;
return text.replace(times, ' $1:$2. ');
}
function fixPeriods(text) {
var periods = /[.](\w+)/g;
return text.replace(periods, '. $1');
}
function fixCapitalsEndSentence(text) {
var capitalsEndSentence = /([A-Z]{2,})([a-z]+)/g;
text = text.replace(capitalsEndSentence, function(match1, match2, match3) {
var len = match2.length - 1;
var newText = match2.substring(0, len) + '. ' + match2.substring(len, len + 1) + match2.substring(len + 1) + match3;
return newText;
});
return text;
}
function fixMultipleSpaces(text) {
var multipleSpaces = /\s+/g;
return text.replace(multipleSpaces, ' ');
}
function fixAll(text) {
text = fixBullets(text);
text = fixBulletEntities(text);
text = fixTimes(text);
text = fixPeriods(text);
text = fixCapitalsEndSentence(text);
text = fixMultipleSpaces(text);
return text;
}