Fix problems with stories that end in newlines

Today I learned that the editor only works properly when the last
<chunk> tag has a <br> inside it at the end.  This last <br> is
invisible and is automatically created by all major browsers when you
use the enter key to type a newline at the end of a story to "prevent
the element from collapsing".  When there's more than one <br> at the
end of the last <chunk>, only the last of those <br>s is invisible, so
if you have three <br>s, they are rendered as two newlines.  This only
applies to the last <chunk>, so if the second last <chunk> has three
<br>s at the end, they are still rendered as three newlines.  Since
the browser is really insistent on doing this, this commit mostly deals
with dynamically creating and deleting <br> tags at the ends of <chunk>
tags as needed to provide a consistent experience, and making sure
that all <br> tags actually go inside of <chunk> tags to prevent
breaking the editor.  The latter behaviour was exhibited by Chrome and
caused a bug when you added a newline at the end of your story using
the editor.
This commit is contained in:
Gnome Ann 2021-10-13 00:42:03 -04:00
parent b3d33cc852
commit aaa0c3374e
1 changed files with 69 additions and 16 deletions

View File

@ -496,6 +496,14 @@ function returnWiList(ar) {
socket.send({'cmd': 'sendwilist', 'data': list}); socket.send({'cmd': 'sendwilist', 'data': list});
} }
function formatChunkInnerText(chunk) {
var text = chunk.innerText.replace(/\u00a0/g, " ");
if((chunk.nextSibling === null || chunk.nextSibling.nodeType !== 1 || chunk.nextSibling.tagName !== "CHUNK") && text.slice(-1) === '\n') {
return text.slice(0, -1);
}
return text;
}
function dosubmit() { function dosubmit() {
var txt = input_text.val().replace(/\u00a0/g, " "); var txt = input_text.val().replace(/\u00a0/g, " ");
if(!memorymode && !gamestarted && ((!adventure || !action_mode) && txt.trim().length == 0)) { if(!memorymode && !gamestarted && ((!adventure || !action_mode) && txt.trim().length == 0)) {
@ -750,11 +758,13 @@ function chunkOnTextInput(event) {
// mobile devices, but the other method is also here as // mobile devices, but the other method is also here as
// a fallback // a fallback
if(document.queryCommandSupported && document.execCommand && document.queryCommandSupported('insertHTML')) { if(document.queryCommandSupported && document.execCommand && document.queryCommandSupported('insertHTML')) {
document.execCommand('insertHTML', false, event.originalEvent.data.slice(0, -1) + '<br/><span id="_EDITOR_SENTINEL_">|</span>'); document.execCommand('insertHTML', false, event.originalEvent.data.slice(0, -1) + '<br id="_EDITOR_LINEBREAK_"/><span id="_EDITOR_SENTINEL_">|</span>');
var t = $('#_EDITOR_SENTINEL_').contents().filter(function() { return this.nodeType === 3; })[0]; var t = $('#_EDITOR_SENTINEL_').contents().filter(function() { return this.nodeType === 3; })[0];
} else { } else {
var t = document.createTextNode('|'); var t = document.createTextNode('|');
r.insertNode(document.createElement('br')); var b = document.createElement('br');
b.id = "_EDITOR_LINEBREAK_";
r.insertNode(b);
r.collapse(false); r.collapse(false);
r.insertNode(t); r.insertNode(t);
} }
@ -771,6 +781,25 @@ function chunkOnTextInput(event) {
// document.execCommand method // document.execCommand method
r.deleteContents(); r.deleteContents();
} }
// In Chrome the added <br/> will go outside of the chunks if we press
// enter at the end of the story in the editor, so this is here
// to put the <br/> back in the right place
var br = $("#_EDITOR_LINEBREAK_")[0];
if(br.parentNode === game_text[0]) {
if(br.previousSibling.nodeType !== 1) {
br.previousSibling.previousSibling.appendChild(br.previousSibling);
}
br.previousSibling.appendChild(br);
r.selectNodeContents(br.parentNode);
s.removeAllRanges();
s.addRange(r);
r.collapse(false);
}
br.id = "";
if(game_text[0].lastChild.tagName === "BR") {
br.parentNode.appendChild(game_text[0].lastChild);
}
return; return;
} }
} }
@ -816,6 +845,10 @@ function downloadStory(format) {
for(var i = 0; i < actionlist.length; i++) { for(var i = 0; i < actionlist.length; i++) {
actionlist_compiled.push(actionlist[i].innerText.replace(/\u00a0/g, " ")); actionlist_compiled.push(actionlist[i].innerText.replace(/\u00a0/g, " "));
} }
var last = actionlist_compiled[actionlist_compiled.length-1];
if(last.slice(-1) === '\n') {
actionlist_compiled[actionlist_compiled.length-1] = last.slice(0, -1);
}
if(format == "plaintext") { if(format == "plaintext") {
var objectURL = URL.createObjectURL(new Blob(actionlist_compiled)); var objectURL = URL.createObjectURL(new Blob(actionlist_compiled));
@ -890,10 +923,10 @@ function applyChunkDeltas(nodes) {
var selected_chunks = buildChunkSetFromNodeArray(getSelectedNodes()); var selected_chunks = buildChunkSetFromNodeArray(getSelectedNodes());
for(var i = 0; i < chunks.length; i++) { for(var i = 0; i < chunks.length; i++) {
var chunk = document.getElementById("n" + chunks[i]); var chunk = document.getElementById("n" + chunks[i]);
if(chunk && chunk.innerText.length != 0 && chunks[i] != '0') { if(chunk && formatChunkInnerText(chunk).length != 0 && chunks[i] != '0') {
if(!selected_chunks.has(chunks[i])) { if(!selected_chunks.has(chunks[i])) {
modified_chunks.delete(chunks[i]); modified_chunks.delete(chunks[i]);
socket.send({'cmd': 'inlineedit', 'chunk': chunks[i], 'data': chunk.innerText.replace(/\u00a0/g, " ")}); socket.send({'cmd': 'inlineedit', 'chunk': chunks[i], 'data': formatChunkInnerText(chunk)});
} }
empty_chunks.delete(chunks[i]); empty_chunks.delete(chunks[i]);
} else { } else {
@ -914,7 +947,7 @@ function syncAllModifiedChunks(including_selected_chunks=false) {
if(including_selected_chunks || !selected_chunks.has(chunks[i])) { if(including_selected_chunks || !selected_chunks.has(chunks[i])) {
modified_chunks.delete(chunks[i]); modified_chunks.delete(chunks[i]);
var chunk = document.getElementById("n" + chunks[i]); var chunk = document.getElementById("n" + chunks[i]);
var data = chunk ? document.getElementById("n" + chunks[i]).innerText.replace(/\u00a0/g, " ") : ""; var data = chunk ? formatChunkInnerText(document.getElementById("n" + chunks[i])) : "";
if(data.length == 0) { if(data.length == 0) {
empty_chunks.add(chunks[i]); empty_chunks.add(chunks[i]);
} else { } else {
@ -927,7 +960,7 @@ function syncAllModifiedChunks(including_selected_chunks=false) {
function restorePrompt() { function restorePrompt() {
if(game_text[0].firstChild && game_text[0].firstChild.nodeType === 3) { if(game_text[0].firstChild && game_text[0].firstChild.nodeType === 3) {
saved_prompt = game_text[0].firstChild.textContent.replace(/\u00a0/g, " "); saved_prompt = formatChunkInnerText(game_text[0].firstChild);
unbindGametext(); unbindGametext();
game_text[0].innerText = ""; game_text[0].innerText = "";
bindGametext(); bindGametext();
@ -961,9 +994,9 @@ function deleteEmptyChunks() {
} }
if(modified_chunks.has('0')) { if(modified_chunks.has('0')) {
modified_chunks.delete(chunks[i]); modified_chunks.delete(chunks[i]);
socket.send({'cmd': 'inlineedit', 'chunk': chunks[i], 'data': document.getElementById("n0").innerText.replace(/\u00a0/g, " ")}); socket.send({'cmd': 'inlineedit', 'chunk': chunks[i], 'data': formatChunkInnerText(document.getElementById("n0"))});
} }
saved_prompt = $("#n0")[0].innerText.replace(/\u00a0/g, " "); saved_prompt = formatChunkInnerText($("#n0")[0]);
} }
function highlightEditingChunks() { function highlightEditingChunks() {
@ -1203,9 +1236,12 @@ $(document).ready(function(){
modified_chunks = new Set(); modified_chunks = new Set();
empty_chunks = new Set(); empty_chunks = new Set();
game_text.html(msg.data); game_text.html(msg.data);
if(game_text[0].lastChild !== null && game_text[0].lastChild.tagName === "CHUNK") {
game_text[0].lastChild.appendChild(document.createElement("br"));
}
bindGametext(); bindGametext();
if(gamestarted) { if(gamestarted) {
saved_prompt = $("#n0")[0].innerText.replace(/\u00a0/g, " "); saved_prompt = formatChunkInnerText($("#n0")[0]);
} }
// Scroll to bottom of text // Scroll to bottom of text
if(newly_loaded) { if(newly_loaded) {
@ -1217,26 +1253,43 @@ $(document).ready(function(){
scrollToBottom(); scrollToBottom();
} else if(msg.cmd == "updatechunk") { } else if(msg.cmd == "updatechunk") {
hideMessage(); hideMessage();
const {index, html} = msg.data; var index = msg.data.index;
const existingChunk = game_text.children(`#n${index}`) var html = msg.data.html;
const newChunk = $(html); var existingChunk = game_text.children('#n' + index);
var newChunk = $(html);
unbindGametext(); unbindGametext();
if (existingChunk.length > 0) { if (existingChunk.length > 0) {
// Update existing chunk // Update existing chunk
if(existingChunk[0].nextSibling === null || existingChunk[0].nextSibling.nodeType !== 1 || existingChunk[0].nextSibling.tagName !== "CHUNK") {
newChunk[0].appendChild(document.createElement("br"));
}
existingChunk.before(newChunk); existingChunk.before(newChunk);
existingChunk.remove(); existingChunk.remove();
} else if (!empty_chunks.has(index.toString())) { } else if (!empty_chunks.has(index.toString())) {
// Append at the end // Append at the end
unbindGametext();
var lc = game_text[0].lastChild;
if(lc.tagName === "CHUNK" && lc.lastChild !== null && lc.lastChild.tagName === "BR") {
lc.removeChild(lc.lastChild);
}
newChunk[0].appendChild(document.createElement("br"));
game_text.append(newChunk); game_text.append(newChunk);
bindGametext();
} }
bindGametext(); bindGametext();
hide([$('#curtain')]); hide([$('#curtain')]);
} else if(msg.cmd == "removechunk") { } else if(msg.cmd == "removechunk") {
hideMessage(); hideMessage();
let index = msg.data; var index = msg.data;
unbindGametext(); var element = game_text.children('#n' + index);
game_text.children(`#n${index}`).remove() // Remove the chunk if(element.length) {
bindGametext(); unbindGametext();
if((element[0].nextSibling === null || element[0].nextSibling.nodeType !== 1 || element[0].nextSibling.tagName !== "CHUNK") && element[0].previousSibling !== null && element[0].previousSibling.tagName === "CHUNK") {
element[0].previousSibling.appendChild(document.createElement("br"));
}
element.remove(); // Remove the chunk
bindGametext();
}
hide([$('#curtain')]); hide([$('#curtain')]);
} else if(msg.cmd == "setgamestate") { } else if(msg.cmd == "setgamestate") {
// Enable or Disable buttons // Enable or Disable buttons