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
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
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() );
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!
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.
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
}
})