Yes that’s is right. The span/div text is added dynamically with JS and isn’t stored for an undo — I’m sure that could be put more accurately.
I seem to remember running into this scenario myself, and creating my own undo button, which is what I have chosen to do in your example.
I am saving the text to localStorage ahead of making any changes e.g. wrapping with a div or span. I then have an undo button that will retrieve the saved text.
I have opted to store that text in an array, so that the undo button can retrieve multiple undos. It is not very sophisticated but seems to work.
An example of an array that will be converted to a JSON format and stored.
// example after making 3 changes
[
'some text. some more text',
'<span>some text.</span> some more text',
'<span>some text.</span> some <div>more</div> text',
]
Storage Functions:
On starting, clear the previous storage.
const TEXTSTORE = "text-store";
// start by clearing previous storage
localStorage.removeItem(TEXTSTORE);
If you are not familiar with JSON, you will want to take a look at JSON.parse and JSON.stringify. The data is stored in string format.
retrieveText function
const retrieveText = () => {
// will retrieve the stored data, which is in string format
// and JSON.parse will convert that into a usable array
const storedText = JSON.parse(localStorage.getItem(TEXTSTORE));
// if nothing has been stored then return with false
if (storedText === null) {
return false;
}
// let's just check that we have an array
if (Array.isArray(storedText)) {
// retrieve last item stored in array.
// pop will mutate the storedText array, removing the last item
const lastText = storedText.pop();
// now the array has one less item, overwrite the previous store
localStorage.setItem(TEXTSTORE, JSON.stringify(storedText));
// return lastText
return lastText;
}
return false;
};
storeText function
const storeText = (text) => {
const storedText = JSON.parse(localStorage.getItem(TEXTSTORE));
// if this is the first time storing then create our text store
// saving an array with our first bit of text
if (storedText === null) {
localStorage.setItem(TEXTSTORE, JSON.stringify([text]));
return; // return early
}
// otherwise add new text to array and store
if (Array.isArray(storedText)) {
// add new text to end of array
storedText.push(text);
// overwrite text store with ammended array
localStorage.setItem(TEXTSTORE, JSON.stringify(storedText));
}
};
Buttons
The buttons will now look like this
document
.querySelectorAll(".wrap-with")
.forEach((button) =>
button.addEventListener("click", (event) => {
const textArea = document.querySelector("#myTextarea");
// store current text before making a change
storeText(textArea.value);
wrapWith(event.target, textArea);
})
);
document
.querySelector(".undo")
.addEventListener("click", () => {
const textArea = document.querySelector("#myTextarea");
// retrieve last text change
const lastText = retrieveText();
// if there is a change then replace the current text with last text
if (lastText) {
textArea.value = lastText;
}
});
Here is a working codepen
Note there are some other changes compared to the last version — had a bit of a play this morning. The HTML now looks like this.
<div class='buttons-container'>
<button
class='wrap-with'
data-tag='div'
data-attribs='id="id-01" classname="div-01"'
>Wrap with div 01</button>
<button
class='wrap-with'
data-tag='div'
data-attribs='id="id-02" classname="div-02"'
>Wrap with div 02</button>
<button
class='wrap-with'
data-tag='span'
data-attribs='id="id-03" classname="span-01"'
>Wrap with span 01</button>
<button
class='wrap-with'
data-tag='span'
data-attribs='id="id-04" classname="span-02"'
>Wrap with span 02</button>
<button class='undo'>Undo</button>
</div>
I have added a tag type and the attribute text that will go inside of the opening tag. Just playing 
Edit: Added semi-colons to line-ends