How to create Medium's Highlighter using React? - javascript

I'm trying to implement highlighter to my blog project using React however I couldn't find any useful library to implement it. Basically I want to implement replica of Medium's Highlighter to my article pages.
After the text selection done, if the user activated the highlighter tool, should be able to highlight the text. Since it requires DOM manipulation I haven't come across the full implementation of the Highlighter the way I wanted to.
What I want to do
User should activate the highlighter.
When the user selects text on the specified area of the articles page, Popover should come up and the user be able select color.
If highlighted text mouseovered, user should see Popover to change the color remove the color.
I found some similar tools;
Link Closest one however it's not getting the which area to highlight and popover modification is not easy.
Link
Link Can't
manipulate the selected text.
I also tried similar JavaScript example however i got error on this methods.link
Currently, I'm trying to write my own component if I can finish it, I will share the source code.
This is the component I wrote, after the text selection done how can i wrap the selected text area with span and can track multiple highlighted texts to remove without breaking the React. I haven't implemented popover yet.
I call this component on article page. Ref comes from the article div.
//articleRef is the id of article area where i want to Highlighter to work.
const Highlighter = ({articleRef}) => {
const { isActive } = useSelector((state) => state.highlighter)
const [selectedText, setSelection] = useState("")
const [selectedNode, setNodeSelection] = useState("")
const [selectedNodes, setNodesSelection] = useState()
useEffect(() => {
document.onselectionchange = function() {
console.log(isActive)
console.log(articleRef.current.id)
// check if the selection done on the article area and check highlighter tool is active
if(articleRef.current.id === 'article-area' && isActive === true ){
console.log("Highlighter is active")
if(document.getSelection() !== (undefined || null ) ){
setNodeSelection(document.getSelection())
}
for(let i = 0; i < selectedNode.rangeCount; i++) {
if( selectedNode.getRangeAt(i) !== (undefined || null )){
console.log(selectedNode.getRangeAt(i))
setSelection(selectedNode.getRangeAt(i).cloneContents())
}
};
console.log(selectedNode)
console.log(selectedText)
}
}
}, [selectedText, selectedNode, isActive, articleRef])
return null;
}

You can use the getSelection API to get the highlighted text. You can make it so that when the user clicks, you get the mouse's position (1), create a element (2) there (which is the tooltip/popup in this case) and use the getSelection API to get the selected text. I recommend using some react hook instead of getSelection as its easier.
(1) - The mouse's position is reported on the event object received by a handler in onmousemove event
(2) - You could also have a hidden element which will be moved to the cursor's position using CSS and make it visible

Related

Change product image by selecting only one attribute in Woocommerce Variable Products

This relates to an online clothing store. We have variable products set up with colour and size attributes, for example a product might have 2 variations; Green/Any Size and Black/Any Size.
There is an image of the product in the corresponding colour for each variation, but at the moment the image doesn't switch unless a colour AND size is selected (I'm aware this is how Woo works out of the box). We would like to make it so that the image switches when JUST a colour is selected.
I have come accross this code on SO and another site. It allows a product variation image to change when only the one main attribute is selected (e.g. color), rather than having to select all variation attributes (e.g color and size) in order for the image to change.
This code was written to work with a plugin, however the person who wrote it mentioned changing line 6 in order to use without plugins.
Can someone help me to adjust this code to my ecommerce store? I know that this can be easly done by someone who understand JS, but since I am not a developer it's really hard for me. I hope someone could help!
var image_to_show = '';
var variations = JSON.parse($(".variations_form").attr("data-product_variations"));
if(variations) {
var first_attr = Object.keys(variations[0].attributes)[0];
// when swatch button is clicked
$("ul[data-attribute_name='"+first_attr+"'] li").click(function() {
var choice = $(this).attr("data-value");
var found = false;
// loop variations JSON
for(const i in variations) {
if(found) continue;
if(variations.hasOwnProperty(i)) {
// if first attribute has the same value as has been selected
if (choice === variations[i].attributes[Object.keys(variations[0].attributes)[0]]) {
// change featured image
image_to_show = variations[i].image_src;
found = true;
}
}
}
});
// after woo additional images has changed the image, change it again
jQuery(".variations_form").on("wc_additional_variation_images_frontend_image_swap_done_callback", function() {
if(image_to_show.length) {
$(".attachment-shop_single").attr("src", image_to_show).removeAttr("srcset");
}
});
}

How to hide autocomplete box on Blur in React?

Can't solve a problem. Could someone help me?
I'm building a form and have an autocomplete box. The problem is that if I click somewhere on a page (except for the suggestion item) it doesn't hide. Where should I add setIsAutoCompelte(false) so that it hides?
State
// if true then show the box
const [isAutoComplete, setIsAutoCompelte] = useState(false);
onHandleChangeInput (if input.length > 3)
// show the box
setIsAutoCompelte(true);
onSuggestionClick (if I click on item in autocoplete box)
// hide the box
setIsAutoCompelte(false);
I do not provide full code because it's too big and I'm sure nobody would take a look at it.
You could have an onClick on App that changes the state if the clicked target didn't have the className of the input. You'd need to apply a className such as "autoCompleteBox" to every element/component where you want isAutoComplete to be true.
const clickAnyWhereBut = (e, className) => {
if (!e.current.target.classList.contains(className)) {
setIsAutoCompelte(false); /* you have a typo for "complete/compelte" */
}
}
<div className="App" onClick={() => clickAnyWhereBut("autoCompleteBox")}></div>
This is a lot easier if you're using redux because you wouldn't need to pass down the setIsAutoCompelte[sic] state.

Populate a div from TinyMCE content dynamically

I'm trying to get the contents from a TinyMCE textarea to populate a button/div as I type. This is to show the client how the button/div will look like when it goes live. Everything else works dynamically, such as the button/div colour, the title and dropdown.
The issue lies with dynamically retrieving the contents from TinyMCE. If I use a standard textarea box it works fine. I want the client to be able to use some of the basic features of TinyMCE.
Kind of how this form field is working. As I'm typing in this box, I can see my text updating below.
My JS is:
$(document).on('change', '#ParentID', function() {
var NTxt = $('#ParentID option:selected').text();
var NVal = document.getElementById("ParentID").value;
NTxt = NTxt.replace(/ # /g,"<br/>");
if(NVal != "0"){
if(NTxt.value != null || NTxt.value != "0" || NTxt.value != undefined){
$("#LTxt").html(NTxt);
}
}else{
$("#LTxt").html('External Link Text/Quote Text');
}
});
$(document).on('keyup', '#Opt2', function() {
$('#LTxt').text($(this).val());
});
Here are some screen grabs:
1. Normal State:
2. Populated title and dropdown "internal link" text:
3. Textarea, populating same place (WITHOUT TINYMCE):
Anyone know how I can do this with TinyMCE? I've tried...
tinymce.get('content id').getContent()
...but it didn't seem to populate dynamically.
This is the key question: How to pass anything typed into the TinyMCE textarea into the button, at the bottom, as the user is typing?
Many thanks in advance,
Glynn
You need to use a variety of events that TinyMCE triggers to know when its content has changed. You can then grab the content from the editor and do whatever you need with it.
Here is an example that shows the actual raw HTML in a neighboring DIV. It can easily be adapted to insert the HTML into an elements so its actually rendered to the page.
http://fiddle.tinymce.com/Gegaab/5
The list of available events is documented here: https://www.tinymce.com/docs/advanced/events/#editorevents
The example uses the keydown and change events in particular but there are many to choose from if those don't fit your needs.

Making a FormBody Shrink and Expand Using JQuery and SharePoint

I am creating a form in sharepoint. I have an ms-FormBody for a text box. I would like for the user to be able to double click the box in order to expand the box and if they double click again, it will shrink back up. Again this is in SharePoint.
EDIT: Thanks to some help from #Thriggle I am very close to the goal I wanted with this project. The problem now is that Whatever you type will only stay on one line (t ext wrapping maybe?). Also The text box doesn't actually take up less space (This is not a big deal but if you can think of anyway to make the rest of the boxes move as this box resizes) and I was wondering if there is a way that the box will be small when program launches.
Based on your screenshots, I'm assuming that you're using Nintex Forms.
For Plain Text Multi-Line Fields
The following will work for plain text multiline fields, but not for rich text or enhanced rich text fields (neither of which are represented by an ordinary textarea element).
In your form settings, in the Custom JavaScript section, you can add the following code:
ExecuteOrDelayUntilScriptLoaded(function(){setTimeout(checkFieldExists,1000);},"sp.js");
function checkFieldExists(){
// Nintex forms load slowly; we'll hold off on running the code
// until we're able to access the element through the DOM.
var field = document.getElementById(DescriptionID);
if(field){
// The field exists, time to attach an event listener.
addExpansionToggleEvent(field);
}else{
// Wait a second, then check again.
setTimeout(checkFieldExists,1000);
}
}
function addExpansionToggleEvent(field){
field.style.height = ""; // remove the default height=100%
field.ondblclick = function(){
var rows = field.getAttribute("rows");
field.setAttribute("rows",+rows < 10 ? 10 : 1);
};
}
This is assuming you added a client ID of DescriptionID to the plain text multiline field that you want to toggle, as shown in your screenshot.
For Rich Text Multi-Line Fields
Rich text fields are (bizarrely) represented by iframes instead of textarea elements.
The following code can be added to your Custom JavaScript section to provide expand/shrink behavior upon double-clicking a rich text field, but note that this does not readjust the way other controls are laid out on the form to account for the field's new size... so it's not especially useful.
ExecuteOrDelayUntilScriptLoaded(function(){setTimeout(checkFieldExists,1000);},"sp.js");
function checkFieldExists(){
var iframes = document.querySelectorAll("iframe[title=\"Rich Text Editor\"]");
if(iframes.length > 0){
addExpansionToggleEvent(iframes);
}else{
setTimeout(checkFieldExists,1000);
}
}
function addExpansionToggleEvent(iframes){
for(var i = 0, len = iframes.length; i < len; i++){
var body = iframes[i].contentDocument.querySelector("body");
(function(container){
body.ondblclick = function(){
container.height = +(container.height) < 140 ? 140 : 30;
};
})(iframes[i]);
}
}
Again, this code is specifically targeted toward rich text field, and will not work for plain text or enhanced rich text fields.

Setting checkbox states after load

I'm having issues setting the visibility of custom Google Maps API v3 overlays. I have a gmaps API page with various overlay, such as polygons, polylines and symbols. Each has an associated text label, made using a custom overlay I adapted from the answer to [this Stack Overflow post][1]
On page load, the actual overlays (polylines, polygons, markers etc - the built in API objects) work correctly. They are displayed based on the default checkbox states. However, the labels are all displayed, regardless if their check box was set by default. If I cycle the checkboxes, everything works correctly.
The overlays are stored as an object called 'overlays' with layout 'description: [polyline, customoverlaylabel]'
Checkbox example code:
<input type="checkbox" id="sun" onclick="refreshCheck('sun')">Sun</input>
This is how I sync whether a display is hidden or visible to the checkbox:
function refreshCheck(overlay) {
var box = document.getElementById(overlay).checked
var lines = overlays[overlay][0]
var text = overlays[overlay][1]
lines.setVisible(box, overlay)
if (box === true) {
text.show()
}
else {
text.hide()
}
}
This code refreshes all the checkmarks, at the end of the javascript head.
var overlayNames = []
for (var k in overlays) overlayNames.push(k)
for (var o in overlayNames) refreshCheck(overlayNames[o])
Here's the hide method of the custom text overlay:
TxtOverlay.prototype.hide = function(){
if (this.div_) {
this.div_.style.visibility = "hidden";
}
}
It's failing the if (this.div_) check, and not doing anything. If I remove the check, it produces an error since this.div_ doesn't exist.
One way to work around it would be to automatically cycle all checkbox states once the page loads (manually doing to solves it). There might be a more fundamental fix. No matter where I attempt to .hide() or .show() a label in my javascript, it doesn't work - it only works when referenced by the checkbox being clicked.
The issue is the moment when you call refreshChecks() .
You assume that the TxtOverlay's already have been added at this time, but that's not the case(that's why the div_'s are still null).
The TXToverlay's (like any object/shape) on a map will be added when the projection of the map is ready.
A possible approach would be:
Instead of using the visible-property of the shapes/markers/etc. to toggle their visibility use the map-property.
The TXTOverlay's are also MVCObject's, you only have to bind the map-property of the TXTOverlay's to the map-property of the related shape.

Categories