Draftjs trying to remove an atomic block - javascript

I am trying to remove an atomic block from the draftjs editor with Modifier.removeRange. From what I can tell I am passing in all the right arguments, but the SelectionState for removal never gets removed.
Here is my code. This also adds in a new atomic block. That part works fine, it's the removal aspect that returns the same contentState that i pass in.
upload_block_selection_state is the SelectionState object for removal. This object is obtained from editorState.getSelection() when it is rendered. It looks like this.
upload_block_selection_state = {
anchorKey: "c1kpk",
anchorOffset: 0,
focusKey: "c1kpk",
focusOffset: 0,
isBackward: false
}
and here is the function that should both remove the upload block and then add a new block. Adding works, removal does nothing.
function addAndRemoveMediaBlock(
entityURL,
entityType,
editorState,
setEditorState,
upload_block_selection_state
){
const contentBeforeAtomicBlock = editorState.getCurrentContent();
const contentAfterSelectionRemoval = Modifier.removeRange(
contentBeforeAtomicBlock,
upload_block_selection_state,
'forward'
);
const contentStateWithEntity = contentAfterSelectionRemoval.createEntity(entityType, 'IMMUTABLE', { src: entityURL });
const entityKey = contentStateWithEntity.getLastCreatedEntityKey();
const editorStateAfterAtomicBlock = AtomicBlockUtils.insertAtomicBlock(
editorState,
entityKey,
entityKey
);
/*
below here relevant only to removing the empty block.
*/
const selectionStateBeforeAtomicBlock = editorState.getSelection();
const anchorKeyBeforeAtomicBlock = selectionStateBeforeAtomicBlock.getAnchorKey();
const contentAfterAtomicBlock = editorStateAfterAtomicBlock.getCurrentContent();
const blockSelectedBefore = contentAfterAtomicBlock.getBlockForKey(anchorKeyBeforeAtomicBlock);
const finalEditorState = (() => {
if(blockSelectedBefore.getLength() === 0){
const keyBefore = blockSelectedBefore.getKey();
const newBlockMap = contentAfterAtomicBlock.blockMap.delete(keyBefore);
const contentWithoutEmptyBlock = contentAfterAtomicBlock.set('blockMap', newBlockMap);
const editorStateWithoutEmptyBlock = EditorState.push(editorStateAfterAtomicBlock, contentWithoutEmptyBlock, 'remove-block');
return EditorState.forceSelection(editorStateWithoutEmptyBlock, contentWithoutEmptyBlock.getSelectionAfter());
}else{
return editorStateAfterAtomicBlock;
}
})();
setEditorState({
editorState: finalEditorState,
});
};

I figured it out.
const contentAfterSelectionRemoval = contentBeforeAtomicBlock.blockMap.delete(blockToRemoveKey);
This seems like an anti-pattern within an Immutable state so I ended up approaching the problem from another angle and didn't actually use this working solution.

set focusOffset to 1 should work if any other parameters are fine.

Related

Async JS validation issues for html textarea

I'm trying to replicate the code in this article:
https://depth-first.com/articles/2020/08/24/smiles-validation-in-the-browser/
What I'm trying to do different is that I'm using a textarea instead of input to take multi-line input. In addition to displaying an error message, I also want to display the entry which doesn't pass the validation.
The original validation script is this:
const path = '/target/wasm32-unknown-unknown/release/smival.wasm';
const read_smiles = instance => {
return smiles => {
const encoder = new TextEncoder();
const encoded = encoder.encode(`${smiles}\0`);
const length = encoded.length;
const pString = instance.exports.alloc(length);
const view = new Uint8Array(
instance.exports.memory.buffer, pString, length
);
view.set(encoded);
return instance.exports.read_smiles(pString);
};
};
const watch = instance => {
const read = read_smiles(instance);
document.querySelector('input').addEventListener('input', e => {
const { target } = e;
if (read(target.value) === 0) {
target.classList.remove('invalid');
} else {
target.classList.add('invalid');
}
});
}
(async () => {
const response = await fetch(path);
const bytes = await response.arrayBuffer();
const wasm = await WebAssembly.instantiate(bytes, { });
watch(wasm.instance);
})();
For working with a textarea, I've changed the watch function to this and added a <p id="indicator"> element to the html to display an error:
const watch = instance => {
const read = read_smiles(instance);
document.querySelector("textarea").addEventListener('input', e => {
const { target } = e;
var lines_array = target.value.split('/n');
var p = document.getElementById("indicator");
p.style.display = "block";
p.innerHTML = "The size of the input is : " + lines_array.length;
if (read(target.value) === 0) {
target.classList.remove('invalid');
} else {
target.classList.add('invalid');
}
});
}
I'm not even able to get a count of entries that fail the validation. I believe this is async js and I'm just a beginner in JavaScript so it's hard to follow what is happening here, especially the part where the function e is referencing itself.
document.querySelector("textarea").addEventListener('input', e => {
const { target } = e;
Can someone please help me in understanding this complicated code and figuring out how to get a count of entries that fail the validation and also printing the string/index of the same for helping the user?
There is a mistake in you code to count entries in the textarea:
var lines_array = target.value.split('\n'); // replace /n with \n
You are asking about the function e is referencing itself:
The destructuring assignment syntax is a JavaScript expression that makes it possible to unpack values from arrays, or properties from objects, into distinct variables. You can find more informations Mdn web docs - Destructuring object

How/When to remove child elements to clear search result?

Trying to clear my search result after I submit a new API call. Tried implementing gallery.remove(galleryItems); at different points but to no avail.
A bit disappointed I couldn't figure it out but happy I was able to get a few async functions going. Anyway, here's the code:
'use strict';
const form = document.querySelector('#searchForm');
const gallery = document.querySelector('.flexbox-container');
const galleryItems = document.getElementsByClassName('flexbox-item');
form.addEventListener('submit', async (e) => {
e.preventDefault();
const userSearch = form.elements.query.value; // grab user input
const res = await getRequest(userSearch); // async func that returns a fully parsed Promise
tvShowMatches(res.data); // looks for matches, creates and appends name + image;
form.elements.query.value = '';
});
const getRequest = async (search) => {
const config = { params: { q: search } };
const res = await axios.get('http://api.tvmaze.com/search/shows', config);
return res;
};
const tvShowMatches = async (shows) => {
for (let result of shows) {
if (result.show.image) {
// new div w/ flexbox-item class + append to gallery
const tvShowMatch = document.createElement('DIV')
tvShowMatch.classList.add('flexbox-item');
gallery.append(tvShowMatch);
// create, fill & append tvShowName to tvShowMatch
const tvShowName = document.createElement('P');
tvShowName.textContent = result.show.name;
tvShowMatch.append(tvShowName);
// create, fill & append tvShowImg to tvShowMatch
const tvShowImg = document.createElement('IMG');
tvShowImg.src = result.show.image.medium;
tvShowMatch.append(tvShowImg);
}
}
};
Thanks
Instead of gallery.remove(galleryItems); consider resetting gallery.innerHTML to an empty string whenever a submit event occurs
Like this:
form.addEventListener('submit', async (e) => {
e.preventDefault();
gallery.innerHTML = ''; // Reset here
const userSearch = form.elements.query.value; // grab user input
const res = await getRequest(userSearch); // async func that returns a fully parsed Promise
tvShowMatches(res.data); // looks for matches, creates and appends name + image;
form.elements.query.value = '';
});
I believe this will do it.. you were close.
const galleryItems = document.getElementsByClassName('flexbox-item');
// to remove
galleryItems.forEach(elem => elem.remove() );

Loop doubles the number of notes in note app

I'm making a small project for keeping notes. Everytime I click "Add a new note" note is added. After clicking second or more times the "Add" button, the loop keeps adding wrong amount of notes. First is 1 then 3,6,10 and so on.
document.querySelector('#newNoteBtn').addEventListener('click', onNewNote);
function onNewNote() {
const title = document.querySelector('#noteTitle').value;
const content = document.querySelector('#noteContent').value;
const note = {
title: title,
content: content,
colour: '#ff1455',
pinned: false,
createDate: new Date()
}
notes.push(note);
console.log(note);
localStorage.setItem(lsNotesKey, JSON.stringify(notes));
const notesFromLocalStorage = JSON.parse(localStorage.getItem(lsNotesKey));
const convertedNotes = notesFromLocalStorage.map( note => {
note.createDate = new Date(note.createDate);
return note;
});
const notesContainer = document.querySelector('main');
for (const note of convertedNotes) {
const htmlNote = document.createElement('section');
const htmlTitle = document.createElement('h1');
const htmlContent = document.createElement('p');
const htmlTime = document.createElement('time');
const htmlButton = document.createElement('button');
htmlNote.classList.add('note');
htmlTitle.innerHTML = note.title;
htmlContent.innerHTML = note.content;
htmlTime.innerHTML = note.createDate.toLocaleString();
htmlButton.innerHTML = 'remove';
htmlButton.addEventListener('click', removeNote);
htmlNote.appendChild(htmlTitle);
htmlNote.appendChild(htmlContent);
htmlNote.appendChild(htmlTime);
htmlNote.appendChild(htmlButton);
notesContainer.appendChild(htmlNote);
}
}
You just never clean up your container, but adding whole set of nodes on each call.
Easiest way to solve that is to cleanup notesContainer:
// ...
const notesContainer = document.querySelector('main');
notesContainer.innerHTML = ''; // <-- this line cleans up your container.
for (const note of convertedNotes) {
// ...
This isn't optimal. From performance prospective, it better to add only newly created note, but this should hightlight the issue.
Looks like you are never clearing the contents of noteContainer:
// before the loop
notesContainer.innerHtml = ""
Good luck!

Cannot filter empty element from an array

I have a problem with this piece of code.
I import input data from a file formated like so and store it in const input:
aabcccccaaa
aaccb
shsudbud
There are no spaces or any other white characters except from '\n' newline.
I get inputs in this way: (LiveServer inside VS Code)
const getData = async () => {
const resp = await fetch("./inputs.txt");
const data = await resp.text();
return data;
};
Then I call:
const myFunc = async () => {
const input = await getData();
const rows = input.split("\n").map(row => row);
rows.forEach(row => {
const charArr = [...row];
console.log(charArr);
});
};
After logging to console first and second row it seems like there is "" (empty string) attached to the end of each of them. The third element is fine so I guess its somehow connected with newline character.
I have also tried creating charArr by doing:
const charArr = Array.from(row);
Or
const charArr = row.split("");
But the outcome was the same.
Later I found this topic: Remove empty elements from an array in Javascript
So I tried:
const charArr = [...row].filter(Boolean);
But the "" is still at the end of charArr created from 1st and 2nd row.
const input = `aabcccccaaa
aaccb
shsudbud`;
const rows = input.split("\n").map(row => row);
rows.forEach(row => {
const charArr = [...row];
console.log(charArr);
});
In this snippet everything works fine. So here is where my questions start:
Why does .filter() method not work properly in this case?
Could this problem browser specific?
Thanks in advance.

Draft.js. How to get all entities data from the ContentState

From official docs I know about 2 methods: get entity by its key and get last created entity. In my case, I also need a method to access all entities from current ContentState.
Is there any method that could perform this? If not, is there a one that can provide all entities keys?
const getEntities = (editorState, entityType = null) => {
const content = editorState.getCurrentContent();
const entities = [];
content.getBlocksAsArray().forEach((block) => {
let selectedEntity = null;
block.findEntityRanges(
(character) => {
if (character.getEntity() !== null) {
const entity = content.getEntity(character.getEntity());
if (!entityType || (entityType && entity.getType() === entityType)) {
selectedEntity = {
entityKey: character.getEntity(),
blockKey: block.getKey(),
entity: content.getEntity(character.getEntity()),
};
return true;
}
}
return false;
},
(start, end) => {
entities.push({...selectedEntity, start, end});
});
});
return entities;
};
How I get the all entities keys:
const contentState = editorState.getCurrentContent()
const entityKeys = Object.keys(convertToRaw(contentState).entityMap)
result:
[0, 1]
then you can call the getEntity(key) method to get the responding entity.
this is how convertToRaw(contentState) looks:
Bao, You will find it inside key called 'blocks'.
convertToRaw(contentState).blocks.map(el=>el.text)
It will give you an array of raw text.
Unfortunatelly your suggested way using convertToRaw doesnt work because it reindexes all keys to ["0", .., "n"], but the real keys differ when you act with the editor. New ones > n will be added and unused will be omitted.
const rawState = convertToRaw(contentState)
const { entityMap } = rawState;
This entityMap will have list of all entities. But this is an expensive conversion. Because, it will convert whole thing to raw. A better way is loop through blocks and check for entity.
You'll have to look at every character:
const { editorState } = this.state; // assumes you store `editorState` on `state`
const contentState = editorState.getCurrentContent();
let entities = [];
contentState.getBlockMap().forEach(block => { // could also use .map() instead
block.findEntityRanges(character => {
const charEntity = character.getEntity();
if (charEntity) { // could be `null`
const contentEntity = contentState.getEntity(charEntity);
entities.push(contentEntity);
}
});
});
Then you could access it via:
entities.forEach((entity, i) => {
if (entity.get('type') === 'ANNOTATION') {
const data = entity.get('data');
// do something
}
})

Categories

Resources