From 7d35f825c6f97b6aa9f1beb0645d833474ef2610 Mon Sep 17 00:00:00 2001 From: henk717 Date: Sat, 25 Sep 2021 16:26:17 +0200 Subject: [PATCH 01/33] Huggingface GPT-J Support Finetune's fork has unofficial support which we supported, but this is not compatible with models designed for the official version. In this update we let models decide which transformers backend to use, and fall back to Neo if they don't choose any. We also add the 6B to the menu and for the time being switch to the github version of transformers to be ahead of the waiting time. (Hopefully we can switch back to the conda version before merging upstream). --- aiserver.py | 16 +++++++++++----- environments/huggingface.yml | 5 +++-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/aiserver.py b/aiserver.py index 9375034b..36a534a7 100644 --- a/aiserver.py +++ b/aiserver.py @@ -44,10 +44,11 @@ class colors: # AI models modellist = [ - ["Custom Neo (GPT-Neo / Converted GPT-J)", "NeoCustom", ""], - ["Custom GPT-2 (eg CloverEdition)", "GPT2Custom", ""], - ["GPT Neo 1.3B", "EleutherAI/gpt-neo-1.3B", "8GB"], - ["GPT Neo 2.7B", "EleutherAI/gpt-neo-2.7B", "16GB"], + ["Load a model from its directory", "NeoCustom", ""], + ["Load an old GPT-2 model (eg CloverEdition)", "GPT2Custom", ""], + ["GPT-Neo 1.3B", "EleutherAI/gpt-neo-1.3B", "8GB"], + ["GPT-Neo 2.7B", "EleutherAI/gpt-neo-2.7B", "16GB"], + ["GPT-J 6B (HF GIT Required)", "EleutherAI/gpt-j-6B", "24GB"], ["GPT-2", "gpt2", "1GB"], ["GPT-2 Med", "gpt2-medium", "2GB"], ["GPT-2 Large", "gpt2-large", "4GB"], @@ -401,7 +402,12 @@ if(not vars.model in ["InferKit", "Colab", "OAI", "ReadOnly"]): # If custom GPT Neo model was chosen if(vars.model == "NeoCustom"): - model = GPTNeoForCausalLM.from_pretrained(vars.custmodpth) + model_config = open(vars.custmodpth + "/config.json", "r") + js = json.load(model_config) + if("architectures" in js): + model = vars.custmodpth + else: + model = GPTNeoForCausalLM.from_pretrained(vars.custmodpth) tokenizer = GPT2Tokenizer.from_pretrained(vars.custmodpth) # Is CUDA available? If so, use GPU, otherwise fall back to CPU if(vars.hascuda): diff --git a/environments/huggingface.yml b/environments/huggingface.yml index d3fbd268..48ffc67d 100644 --- a/environments/huggingface.yml +++ b/environments/huggingface.yml @@ -11,7 +11,8 @@ dependencies: - python=3.8.* - cudatoolkit=11.1 - tensorflow-gpu - - transformers - pip + - git - pip: - - flask-cloudflared \ No newline at end of file + - flask-cloudflared + - git+https://github.com/huggingface/transformers#transformer \ No newline at end of file From c9290d02dce291160b808e4b6d2551760e684470 Mon Sep 17 00:00:00 2001 From: henk717 Date: Sat, 25 Sep 2021 16:50:24 +0200 Subject: [PATCH 02/33] Update aiserver.py Better way of checking for the model type --- aiserver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aiserver.py b/aiserver.py index 36a534a7..f522413c 100644 --- a/aiserver.py +++ b/aiserver.py @@ -404,7 +404,7 @@ if(not vars.model in ["InferKit", "Colab", "OAI", "ReadOnly"]): if(vars.model == "NeoCustom"): model_config = open(vars.custmodpth + "/config.json", "r") js = json.load(model_config) - if("architectures" in js): + if("model_type" in js): model = vars.custmodpth else: model = GPTNeoForCausalLM.from_pretrained(vars.custmodpth) From 5893e495b6d75704d8a1d95ae5bc2061a3e64bbf Mon Sep 17 00:00:00 2001 From: Gnome Ann <> Date: Sat, 25 Sep 2021 11:41:15 -0400 Subject: [PATCH 03/33] Change AutoModel to AutoModelForCausalLM This fixes breakmodel mode for the official models from the model selection menu. --- aiserver.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aiserver.py b/aiserver.py index 9375034b..d5f3fd86 100644 --- a/aiserver.py +++ b/aiserver.py @@ -397,7 +397,7 @@ print("{0}OK!{1}".format(colors.GREEN, colors.END)) if(not vars.model in ["InferKit", "Colab", "OAI", "ReadOnly"]): if(not vars.noai): print("{0}Initializing transformers, please wait...{1}".format(colors.PURPLE, colors.END)) - from transformers import pipeline, GPT2Tokenizer, GPT2LMHeadModel, GPTNeoForCausalLM, GPTNeoModel, AutoModel + from transformers import pipeline, GPT2Tokenizer, GPT2LMHeadModel, GPTNeoForCausalLM, GPTNeoModel, AutoModelForCausalLM # If custom GPT Neo model was chosen if(vars.model == "NeoCustom"): @@ -460,7 +460,7 @@ if(not vars.model in ["InferKit", "Colab", "OAI", "ReadOnly"]): generator = pipeline('text-generation', model=vars.model, device=0) elif(vars.breakmodel): # Use both RAM and VRAM (breakmodel) import breakmodel - model = AutoModel.from_pretrained(vars.model) + model = AutoModelForCausalLM.from_pretrained(vars.model) n_layers = model.config.num_layers breakmodel.total_blocks = n_layers model.half().to('cpu') From c3781e0e2f2f84a340bae65542e819d54610945a Mon Sep 17 00:00:00 2001 From: Gnome Ann <> Date: Mon, 27 Sep 2021 13:11:15 -0400 Subject: [PATCH 04/33] Rewrite the code for the editor for better browser compat Does not work well on mobile. That's on my to-do list, don't you worry! --- static/application.js | 314 +++++++++++++++++++++++++++------------ static/custom.css | 4 +- static/rangy-core.min.js | 11 ++ templates/index.html | 3 +- 4 files changed, 231 insertions(+), 101 deletions(-) create mode 100644 static/rangy-core.min.js diff --git a/static/application.js b/static/application.js index 2431db49..197c0494 100644 --- a/static/application.js +++ b/static/application.js @@ -69,8 +69,10 @@ var gamestarted = false; var editmode = false; var connected = false; var newly_loaded = true; -var current_editing_chunk = null; -var chunk_conflict = false; +var modified_chunks = new Set(); +var empty_chunks = new Set(); +var mutation_observer = null; +var gametext_bound = false; var sman_allow_delete = false; var sman_allow_rename = false; @@ -720,9 +722,6 @@ function setadventure(state) { function autofocus(event) { if(connected) { - if(event.target.tagName == "CHUNK") { - current_editing_chunk = event.target; - } event.target.focus(); } else { event.preventDefault(); @@ -740,65 +739,6 @@ function chunkOnKeyDown(event) { return; } - // Allow left and right arrow keys (and backspace) to move between chunks - switch(event.keyCode) { - case 37: // left - case 39: // right - var old_range = getSelection().getRangeAt(0); - var old_range_start = old_range.startOffset; - var old_range_end = old_range.endOffset; - var old_range_ancestor = old_range.commonAncestorContainer; - var old_range_start_container = old_range.startContainer; - var old_range_end_container = old_range.endContainer; - setTimeout(function () { - // Wait a few milliseconds and check if the caret has moved - var new_selection = getSelection(); - var new_range = new_selection.getRangeAt(0); - if(old_range_start != new_range.startOffset || old_range_end != new_range.endOffset || old_range_ancestor != new_range.commonAncestorContainer || old_range_start_container != new_range.startContainer || old_range_end_container != new_range.endContainer) { - return; - } - // If it hasn't moved, we're at the beginning or end of a chunk - // and the caret must be moved to a different chunk - var chunk = document.activeElement; - switch(event.keyCode) { - case 37: // left - if((chunk = chunk.previousSibling) && chunk.tagName == "CHUNK") { - var range = document.createRange(); - range.selectNodeContents(chunk); - range.collapse(false); - new_selection.removeAllRanges(); - new_selection.addRange(range); - } - break; - - case 39: // right - if((chunk = chunk.nextSibling) && chunk.tagName == "CHUNK") { - chunk.focus(); - } - } - }, 2); - return; - - case 8: // backspace - var old_length = document.activeElement.innerText.length; - setTimeout(function () { - // Wait a few milliseconds and compare the chunk's length - if(old_length != document.activeElement.innerText.length) { - return; - } - // If it's the same, we're at the beginning of a chunk - if((chunk = document.activeElement.previousSibling) && chunk.tagName == "CHUNK") { - var range = document.createRange(); - var selection = getSelection(); - range.selectNodeContents(chunk); - range.collapse(false); - selection.removeAllRanges(); - selection.addRange(range); - } - }, 2); - return - } - // Don't allow any edits if not connected to server if(!connected) { event.preventDefault(); @@ -820,25 +760,6 @@ function chunkOnKeyDown(event) { } } -function submitEditedChunk(event) { - // Don't do anything if the current chunk hasn't been edited or if someone - // else overwrote it while you were busy lollygagging - if(current_editing_chunk === null || chunk_conflict) { - chunk_conflict = false; - return; - } - - var chunk = current_editing_chunk; - current_editing_chunk = null; - - // Submit the edited chunk if it's not empty, otherwise delete it - if(chunk.innerText.length) { - socket.send({'cmd': 'inlineedit', 'chunk': chunk.getAttribute("n"), 'data': chunk.innerText.replace(/\u00a0/g, " ")}); - } else { - socket.send({'cmd': 'inlinedelete', 'data': chunk.getAttribute("n")}); - } -} - function downloadStory(format) { var filename_without_extension = storyname !== null ? storyname : "untitled"; @@ -890,6 +811,179 @@ function downloadStory(format) { URL.revokeObjectURL(objectURL); } +function buildChunkSetFromNodeArray(nodes) { + var set = new Set(); + for(var i = 0; i < nodes.length; i++) { + node = nodes[i]; + while(node !== null && node.tagName !== "CHUNK") { + node = node.parentNode; + } + if(node === null) { + continue; + } + set.add(node.getAttribute("n")); + } + return set; +} + +function getSelectedNodes() { + var range = rangy.getSelection().getRangeAt(0); // rangy is not a typo + var nodes = range.getNodes([1,3]); + nodes.push(range.startContainer); + nodes.push(range.endContainer); + return nodes +} + +function applyChunkDeltas(nodes) { + var chunks = Array.from(buildChunkSetFromNodeArray(nodes)); + for(var i = 0; i < chunks.length; i++) { + modified_chunks.add(chunks[i]); + } + setTimeout(function() { + var chunks = Array.from(modified_chunks); + var selected_chunks = buildChunkSetFromNodeArray(getSelectedNodes()); + for(var i = 0; i < chunks.length; i++) { + var chunk = document.getElementById("n" + chunks[i]); + if(chunk && chunk.innerText.length != 0) { + if(!selected_chunks.has(chunks[i])) { + modified_chunks.delete(chunks[i]); + socket.send({'cmd': 'inlineedit', 'chunk': chunks[i], 'data': chunk.innerText.replace(/\u00a0/g, " ")}); + } + empty_chunks.delete(chunks[i]); + } else { + if(!selected_chunks.has(chunks[i])) { + modified_chunks.delete(chunks[i]); + socket.send({'cmd': 'inlineedit', 'chunk': chunks[i], 'data': ''}); + } + empty_chunks.add(chunks[i]); + } + } + }, 2); +} + +function syncAllModifiedChunks(including_selected_chunks=false) { + var chunks = Array.from(modified_chunks); + var selected_chunks = buildChunkSetFromNodeArray(getSelectedNodes()); + for(var i = 0; i < chunks.length; i++) { + if(including_selected_chunks || !selected_chunks.has(chunks[i])) { + modified_chunks.delete(chunks[i]); + var chunk = document.getElementById("n" + chunks[i]); + var data = chunk ? document.getElementById("n" + chunks[i]).innerText.replace(/\u00a0/g, " ") : ""; + if(data.length == 0) { + empty_chunks.add(chunks[i]); + } else { + empty_chunks.delete(chunks[i]); + } + socket.send({'cmd': 'inlineedit', 'chunk': chunks[i], 'data': data}); + } + } +} + +function deleteEmptyChunks() { + var chunks = Array.from(empty_chunks); + for(var i = 0; i < chunks.length; i++) { + empty_chunks.delete(chunks[i]); + socket.send({'cmd': 'inlinedelete', 'data': chunks[i]}); + } +} + +function highlightEditingChunks() { + var chunks = $('chunk.editing').toArray(); + var selected_chunks = buildChunkSetFromNodeArray(getSelectedNodes()); + for(var i = 0; i < chunks.length; i++) { + var chunk = chunks[i]; + if(!selected_chunks.has(chunks[i].getAttribute("n"))) { + unbindGametext(); + $(chunk).removeClass('editing'); + bindGametext(); + } + } + chunks = Array.from(selected_chunks); + for(var i = 0; i < chunks.length; i++) { + var chunk = $("#n"+chunks[i]); + unbindGametext(); + chunk.addClass('editing'); + bindGametext(); + } +} + +// (Desktop client only) This gets run every time the text in a chunk is edited +// or a chunk is deleted +function chunkOnDOMMutate(mutations, observer) { + if(!gametext_bound) { + return; + } + var nodes = []; + for(var i = 0; i < mutations.length; i++) { + var mutation = mutations[i]; + if(mutation.type === "childList") { + nodes = nodes.concat(Array.from(mutation.addedNodes), Array.from(mutation.removedNodes)); + } else { + nodes.push(mutation.target); + } + } + applyChunkDeltas(nodes); +} + +// This gets run every time you try to paste text into the editor +function chunkOnPaste(event) { + if(!gametext_bound) { + return; + } + // If possible, intercept paste events into the editor in order to always + // paste as plaintext + if(event.originalEvent.clipboardData && document.queryCommandSupported && document.execCommand && document.queryCommandSupported('insertText')) { + event.preventDefault(); + document.execCommand('insertText', false, event.originalEvent.clipboardData.getData('text/plain')); + } +} + +// (Desktop client only) This gets run every time the caret moves in the editor +function chunkOnSelectionChange(event) { + if(!gametext_bound) { + return; + } + setTimeout(function() { + syncAllModifiedChunks(); + setTimeout(function() { + highlightEditingChunks(); + }, 2); + }, 2); +} + +// (Desktop client only) This gets run when you defocus the editor by clicking +// outside of the editor or by pressing escape or tab +function chunkOnFocusOut(event) { + if(!gametext_bound || event.target !== gametext) { + return; + } + setTimeout(function() { + if(document.activeElement === gametext || gametext.contains(document.activeElement)) { + return; + } + syncAllModifiedChunks(true); + setTimeout(function() { + var blurred = $("#gametext")[0] !== document.activeElement; + if(blurred) { + deleteEmptyChunks(); + } + setTimeout(function() { + $("chunk").removeClass('editing'); + }, 2); + }, 2); + }, 2); +} + +function bindGametext() { + mutation_observer.observe($("#gametext")[0], {characterData: true, childList: true, subtree: true}); + gametext_bound = true; +} + +function unbindGametext() { + mutation_observer.disconnect(); + gametext_bound = false; +} + //=================================================================// // READY/RUNTIME //=================================================================// @@ -973,11 +1067,11 @@ $(document).ready(function(){ format_menu.html(""); wi_menu.html(""); // Set up "Allow Editing" - $('body').on('input', autofocus).on('keydown', 'chunk', chunkOnKeyDown).on('focusout', 'chunk', submitEditedChunk); - $('#allowediting').prop('checked', allowedit).prop('disabled', false).change().on('change', function () { + $('body').on('input', autofocus); + $('#allowediting').prop('checked', allowedit).prop('disabled', false).change().off('change').on('change', function () { if(allowtoggle) { - allowedit = $(this).prop('checked') - $("chunk").attr('contenteditable', allowedit) + allowedit = $(this).prop('checked'); + $(gametext).attr('contenteditable', allowedit); } }); } else if(msg.cmd == "updatescreen") { @@ -987,13 +1081,11 @@ $(document).ready(function(){ action_mode = 0; changemode(); } - // Send game content to Game Screen - if(allowedit && document.activeElement.tagName == "CHUNK") { - chunk_conflict = true; - } + unbindGametext(); + modified_chunks = new Set(); + empty_chunks = new Set(); game_text.html(msg.data); - // Make content editable if need be - $('chunk').attr('contenteditable', allowedit); + bindGametext(); // Scroll to bottom of text if(newly_loaded) { scrollToBottom(); @@ -1007,15 +1099,16 @@ $(document).ready(function(){ const {index, html, last} = msg.data; const existingChunk = game_text.children(`#n${index}`) const newChunk = $(html); + unbindGametext(); if (existingChunk.length > 0) { // Update existing chunk existingChunk.before(newChunk); existingChunk.remove(); - } else { + } else if (!empty_chunks.has(index.toString())) { // Append at the end game_text.append(newChunk); } - newChunk.attr('contenteditable', allowedit); + bindGametext(); hide([$('#curtain')]); if(last) { // Scroll to bottom of text if it's the last element @@ -1024,8 +1117,9 @@ $(document).ready(function(){ } else if(msg.cmd == "removechunk") { hideMessage(); let index = msg.data; - // Remove the chunk - game_text.children(`#n${index}`).remove() + unbindGametext(); + game_text.children(`#n${index}`).remove() // Remove the chunk + bindGametext(); hide([$('#curtain')]); } else if(msg.cmd == "setgamestate") { // Enable or Disable buttons @@ -1252,7 +1346,31 @@ $(document).ready(function(){ connect_status.removeClass("color_green"); connect_status.addClass("color_orange"); }); - + + // Register editing events (desktop) + $(gametext).on('keydown', + chunkOnKeyDown + ).on('paste', + chunkOnPaste + ).on('focus', + chunkOnSelectionChange + ).on('keydown', + chunkOnSelectionChange + ).on('focusout', + chunkOnFocusOut + ); + mutation_observer = new MutationObserver(chunkOnDOMMutate); + + // This is required for the editor to work correctly in Firefox on desktop + // because the gods of HTML and JavaScript say so + $(document.body).on('focusin', function(event) { + setTimeout(function() { + if(document.activeElement !== gametext && gametext.contains(document.activeElement)) { + gametext.focus(); + } + }, 2); + }); + // Bind actions to UI buttons button_send.on("click", function(ev) { dosubmit(); diff --git a/static/custom.css b/static/custom.css index da040295..634a2eb8 100644 --- a/static/custom.css +++ b/static/custom.css @@ -11,12 +11,12 @@ chunk { font-weight: bold; } -chunk[contenteditable="true"]:focus, chunk[contenteditable="true"]:focus * { +chunk.editing, chunk.editing * { color: #cdf !important; font-weight: normal !important; } -chunk, chunk * { +#gametext, chunk, chunk * { outline: 0px solid transparent; } diff --git a/static/rangy-core.min.js b/static/rangy-core.min.js new file mode 100644 index 00000000..ea65b24d --- /dev/null +++ b/static/rangy-core.min.js @@ -0,0 +1,11 @@ +/** + * Rangy, a cross-browser JavaScript range and selection library + * https://github.com/timdown/rangy + * + * Copyright 2015, Tim Down + * Licensed under the MIT license. + * Version: 1.3.0 + * Build date: 10 May 2015 + */ +!function(e,t){"function"==typeof define&&define.amd?define(e):"undefined"!=typeof module&&"object"==typeof exports?module.exports=e():t.rangy=e()}(function(){function e(e,t){var n=typeof e[t];return n==N||!(n!=C||!e[t])||"unknown"==n}function t(e,t){return!(typeof e[t]!=C||!e[t])}function n(e,t){return typeof e[t]!=E}function r(e){return function(t,n){for(var r=n.length;r--;)if(!e(t,n[r]))return!1;return!0}}function o(e){return e&&O(e,T)&&D(e,w)}function i(e){return t(e,"body")?e.body:e.getElementsByTagName("body")[0]}function a(t){typeof console!=E&&e(console,"log")&&console.log(t)}function s(e,t){b&&t?alert(e):a(e)}function c(e){I.initialized=!0,I.supported=!1,s("Rangy is not supported in this environment. Reason: "+e,I.config.alertOnFail)}function d(e){s("Rangy warning: "+e,I.config.alertOnWarn)}function f(e){return e.message||e.description||String(e)}function u(){if(b&&!I.initialized){var t,n=!1,r=!1;e(document,"createRange")&&(t=document.createRange(),O(t,y)&&D(t,S)&&(n=!0));var s=i(document);if(!s||"body"!=s.nodeName.toLowerCase())return void c("No body element found");if(s&&e(s,"createTextRange")&&(t=s.createTextRange(),o(t)&&(r=!0)),!n&&!r)return void c("Neither Range nor TextRange are available");I.initialized=!0,I.features={implementsDomRange:n,implementsTextRange:r};var d,u;for(var l in x)(d=x[l])instanceof p&&d.init(d,I);for(var h=0,g=M.length;g>h;++h)try{M[h](I)}catch(m){u="Rangy init listener threw an exception. Continuing. Detail: "+f(m),a(u)}}}function l(e,t,n){n&&(e+=" in module "+n.name),I.warn("DEPRECATED: "+e+" is deprecated. Please use "+t+" instead.")}function h(e,t,n,r){e[t]=function(){return l(t,n,r),e[n].apply(e,P.toArray(arguments))}}function g(e){e=e||window,u();for(var t=0,n=k.length;n>t;++t)k[t](e)}function p(e,t,n){this.name=e,this.dependencies=t,this.initialized=!1,this.supported=!1,this.initializer=n}function m(e,t,n){var r=new p(e,t,function(t){if(!t.initialized){t.initialized=!0;try{n(I,t),t.supported=!0}catch(r){var o="Module '"+e+"' failed to load: "+f(r);a(o),r.stack&&a(r.stack)}}});return x[e]=r,r}function R(){}function v(){}var C="object",N="function",E="undefined",S=["startContainer","startOffset","endContainer","endOffset","collapsed","commonAncestorContainer"],y=["setStart","setStartBefore","setStartAfter","setEnd","setEndBefore","setEndAfter","collapse","selectNode","selectNodeContents","compareBoundaryPoints","deleteContents","extractContents","cloneContents","insertNode","surroundContents","cloneRange","toString","detach"],w=["boundingHeight","boundingLeft","boundingTop","boundingWidth","htmlText","text"],T=["collapse","compareEndPoints","duplicate","moveToElementText","parentElement","select","setEndPoint","getBoundingClientRect"],O=r(e),_=r(t),D=r(n),A=[].forEach?function(e,t){e.forEach(t)}:function(e,t){for(var n=0,r=e.length;r>n;++n)t(e[n],n)},x={},b=typeof window!=E&&typeof document!=E,P={isHostMethod:e,isHostObject:t,isHostProperty:n,areHostMethods:O,areHostObjects:_,areHostProperties:D,isTextRange:o,getBody:i,forEach:A},I={version:"1.3.0",initialized:!1,isBrowser:b,supported:!0,util:P,features:{},modules:x,config:{alertOnFail:!1,alertOnWarn:!1,preferTextRange:!1,autoInitialize:typeof rangyAutoInitialize==E?!0:rangyAutoInitialize}};I.fail=c,I.warn=d;var B;({}).hasOwnProperty?(P.extend=B=function(e,t,n){var r,o;for(var i in t)t.hasOwnProperty(i)&&(r=e[i],o=t[i],n&&null!==r&&"object"==typeof r&&null!==o&&"object"==typeof o&&B(r,o,!0),e[i]=o);return t.hasOwnProperty("toString")&&(e.toString=t.toString),e},P.createOptions=function(e,t){var n={};return B(n,t),e&&B(n,e),n}):c("hasOwnProperty not supported"),b||c("Rangy can only run in a browser"),function(){var e;if(b){var t=document.createElement("div");t.appendChild(document.createElement("span"));var n=[].slice;try{1==n.call(t.childNodes,0)[0].nodeType&&(e=function(e){return n.call(e,0)})}catch(r){}}e||(e=function(e){for(var t=[],n=0,r=e.length;r>n;++n)t[n]=e[n];return t}),P.toArray=e}();var H;b&&(e(document,"addEventListener")?H=function(e,t,n){e.addEventListener(t,n,!1)}:e(document,"attachEvent")?H=function(e,t,n){e.attachEvent("on"+t,n)}:c("Document does not have required addEventListener or attachEvent method"),P.addListener=H);var M=[];P.deprecationNotice=l,P.createAliasForDeprecatedMethod=h,I.init=u,I.addInitListener=function(e){I.initialized?e(I):M.push(e)};var k=[];I.addShimListener=function(e){k.push(e)},b&&(I.shim=I.createMissingNativeApi=g,h(I,"createMissingNativeApi","shim")),p.prototype={init:function(){for(var e,t,n=this.dependencies||[],r=0,o=n.length;o>r;++r){if(t=n[r],e=x[t],!(e&&e instanceof p))throw new Error("required module '"+t+"' not found");if(e.init(),!e.supported)throw new Error("required module '"+t+"' not supported")}this.initializer(this)},fail:function(e){throw this.initialized=!0,this.supported=!1,new Error(e)},warn:function(e){I.warn("Module "+this.name+": "+e)},deprecationNotice:function(e,t){I.warn("DEPRECATED: "+e+" in module "+this.name+" is deprecated. Please use "+t+" instead")},createError:function(e){return new Error("Error in Rangy "+this.name+" module: "+e)}},I.createModule=function(e){var t,n;2==arguments.length?(t=arguments[1],n=[]):(t=arguments[2],n=arguments[1]);var r=m(e,n,t);I.initialized&&I.supported&&r.init()},I.createCoreModule=function(e,t,n){m(e,t,n)},I.RangePrototype=R,I.rangePrototype=new R,I.selectionPrototype=new v,I.createCoreModule("DomUtil",[],function(e,t){function n(e){var t;return typeof e.namespaceURI==b||null===(t=e.namespaceURI)||"http://www.w3.org/1999/xhtml"==t}function r(e){var t=e.parentNode;return 1==t.nodeType?t:null}function o(e){for(var t=0;e=e.previousSibling;)++t;return t}function i(e){switch(e.nodeType){case 7:case 10:return 0;case 3:case 8:return e.length;default:return e.childNodes.length}}function a(e,t){var n,r=[];for(n=e;n;n=n.parentNode)r.push(n);for(n=t;n;n=n.parentNode)if(M(r,n))return n;return null}function s(e,t,n){for(var r=n?t:t.parentNode;r;){if(r===e)return!0;r=r.parentNode}return!1}function c(e,t){return s(e,t,!0)}function d(e,t,n){for(var r,o=n?e:e.parentNode;o;){if(r=o.parentNode,r===t)return o;o=r}return null}function f(e){var t=e.nodeType;return 3==t||4==t||8==t}function u(e){if(!e)return!1;var t=e.nodeType;return 3==t||8==t}function l(e,t){var n=t.nextSibling,r=t.parentNode;return n?r.insertBefore(e,n):r.appendChild(e),e}function h(e,t,n){var r=e.cloneNode(!1);if(r.deleteData(0,t),e.deleteData(t,e.length-t),l(r,e),n)for(var i,a=0;i=n[a++];)i.node==e&&i.offset>t?(i.node=r,i.offset-=t):i.node==e.parentNode&&i.offset>o(e)&&++i.offset;return r}function g(e){if(9==e.nodeType)return e;if(typeof e.ownerDocument!=b)return e.ownerDocument;if(typeof e.document!=b)return e.document;if(e.parentNode)return g(e.parentNode);throw t.createError("getDocument: no document found for node")}function p(e){var n=g(e);if(typeof n.defaultView!=b)return n.defaultView;if(typeof n.parentWindow!=b)return n.parentWindow;throw t.createError("Cannot get a window object for node")}function m(e){if(typeof e.contentDocument!=b)return e.contentDocument;if(typeof e.contentWindow!=b)return e.contentWindow.document;throw t.createError("getIframeDocument: No Document object found for iframe element")}function R(e){if(typeof e.contentWindow!=b)return e.contentWindow;if(typeof e.contentDocument!=b)return e.contentDocument.defaultView;throw t.createError("getIframeWindow: No Window object found for iframe element")}function v(e){return e&&P.isHostMethod(e,"setTimeout")&&P.isHostObject(e,"document")}function C(e,t,n){var r;if(e?P.isHostProperty(e,"nodeType")?r=1==e.nodeType&&"iframe"==e.tagName.toLowerCase()?m(e):g(e):v(e)&&(r=e.document):r=document,!r)throw t.createError(n+"(): Parameter must be a Window object or DOM node");return r}function N(e){for(var t;t=e.parentNode;)e=t;return e}function E(e,n,r,i){var s,c,f,u,l;if(e==r)return n===i?0:i>n?-1:1;if(s=d(r,e,!0))return n<=o(s)?-1:1;if(s=d(e,r,!0))return o(s)[index:"+o(e)+",length:"+e.childNodes.length+"]["+(e.innerHTML||"[innerHTML not supported]").slice(0,25)+"]"}return e.nodeName}function w(e){for(var t,n=g(e).createDocumentFragment();t=e.firstChild;)n.appendChild(t);return n}function T(e,t,n){var r=I(e),o=e.createElement("div");o.contentEditable=""+!!n,t&&(o.innerHTML=t);var i=r.firstChild;return i?r.insertBefore(o,i):r.appendChild(o),o}function O(e){return e.parentNode.removeChild(e)}function _(e){this.root=e,this._next=e}function D(e){return new _(e)}function A(e,t){this.node=e,this.offset=t}function x(e){this.code=this[e],this.codeName=e,this.message="DOMException: "+this.codeName}var b="undefined",P=e.util,I=P.getBody;P.areHostMethods(document,["createDocumentFragment","createElement","createTextNode"])||t.fail("document missing a Node creation method"),P.isHostMethod(document,"getElementsByTagName")||t.fail("document missing getElementsByTagName method");var B=document.createElement("div");P.areHostMethods(B,["insertBefore","appendChild","cloneNode"]||!P.areHostObjects(B,["previousSibling","nextSibling","childNodes","parentNode"]))||t.fail("Incomplete Element implementation"),P.isHostProperty(B,"innerHTML")||t.fail("Element is missing innerHTML property");var H=document.createTextNode("test");P.areHostMethods(H,["splitText","deleteData","insertData","appendData","cloneNode"]||!P.areHostObjects(B,["previousSibling","nextSibling","childNodes","parentNode"])||!P.areHostProperties(H,["data"]))||t.fail("Incomplete Text Node implementation");var M=function(e,t){for(var n=e.length;n--;)if(e[n]===t)return!0;return!1},k=!1;!function(){var t=document.createElement("b");t.innerHTML="1";var n=t.firstChild;t.innerHTML="
",k=S(n),e.features.crashyTextNodes=k}();var L;typeof window.getComputedStyle!=b?L=function(e,t){return p(e).getComputedStyle(e,null)[t]}:typeof document.documentElement.currentStyle!=b?L=function(e,t){return e.currentStyle?e.currentStyle[t]:""}:t.fail("No means of obtaining computed style properties found"),_.prototype={_current:null,hasNext:function(){return!!this._next},next:function(){var e,t,n=this._current=this._next;if(this._current)if(e=n.firstChild)this._next=e;else{for(t=null;n!==this.root&&!(t=n.nextSibling);)n=n.parentNode;this._next=t}return this._current},detach:function(){this._current=this._next=this.root=null}},A.prototype={equals:function(e){return!!e&&this.node===e.node&&this.offset==e.offset},inspect:function(){return"[DomPosition("+y(this.node)+":"+this.offset+")]"},toString:function(){return this.inspect()}},x.prototype={INDEX_SIZE_ERR:1,HIERARCHY_REQUEST_ERR:3,WRONG_DOCUMENT_ERR:4,NO_MODIFICATION_ALLOWED_ERR:7,NOT_FOUND_ERR:8,NOT_SUPPORTED_ERR:9,INVALID_STATE_ERR:11,INVALID_NODE_TYPE_ERR:24},x.prototype.toString=function(){return this.message},e.dom={arrayContains:M,isHtmlNamespace:n,parentElement:r,getNodeIndex:o,getNodeLength:i,getCommonAncestor:a,isAncestorOf:s,isOrIsAncestorOf:c,getClosestAncestorIn:d,isCharacterDataNode:f,isTextOrCommentNode:u,insertAfter:l,splitDataNode:h,getDocument:g,getWindow:p,getIframeWindow:R,getIframeDocument:m,getBody:I,isWindow:v,getContentDocument:C,getRootContainer:N,comparePoints:E,isBrokenNode:S,inspectNode:y,getComputedStyleProperty:L,createTestElement:T,removeNode:O,fragmentFromNodeChildren:w,createIterator:D,DomPosition:A},e.DOMException=x}),I.createCoreModule("DomRange",["DomUtil"],function(e){function t(e,t){return 3!=e.nodeType&&(F(e,t.startContainer)||F(e,t.endContainer))}function n(e){return e.document||j(e.startContainer)}function r(e){return Q(e.startContainer)}function o(e){return new M(e.parentNode,W(e))}function i(e){return new M(e.parentNode,W(e)+1)}function a(e,t,n){var r=11==e.nodeType?e.firstChild:e;return L(t)?n==t.length?B.insertAfter(e,t):t.parentNode.insertBefore(e,0==n?t:U(t,n)):n>=t.childNodes.length?t.appendChild(e):t.insertBefore(e,t.childNodes[n]),r}function s(e,t,r){if(w(e),w(t),n(t)!=n(e))throw new k("WRONG_DOCUMENT_ERR");var o=z(e.startContainer,e.startOffset,t.endContainer,t.endOffset),i=z(e.endContainer,e.endOffset,t.startContainer,t.startOffset);return r?0>=o&&i>=0:0>o&&i>0}function c(e){for(var t,r,o,i=n(e.range).createDocumentFragment();r=e.next();){if(t=e.isPartiallySelectedSubtree(),r=r.cloneNode(!t),t&&(o=e.getSubtreeIterator(),r.appendChild(c(o)),o.detach()),10==r.nodeType)throw new k("HIERARCHY_REQUEST_ERR");i.appendChild(r)}return i}function d(e,t,n){var r,o;n=n||{stop:!1};for(var i,a;i=e.next();)if(e.isPartiallySelectedSubtree()){if(t(i)===!1)return void(n.stop=!0);if(a=e.getSubtreeIterator(),d(a,t,n),a.detach(),n.stop)return}else for(r=B.createIterator(i);o=r.next();)if(t(o)===!1)return void(n.stop=!0)}function f(e){for(var t;e.next();)e.isPartiallySelectedSubtree()?(t=e.getSubtreeIterator(),f(t),t.detach()):e.remove()}function u(e){for(var t,r,o=n(e.range).createDocumentFragment();t=e.next();){if(e.isPartiallySelectedSubtree()?(t=t.cloneNode(!1),r=e.getSubtreeIterator(),t.appendChild(u(r)),r.detach()):e.remove(),10==t.nodeType)throw new k("HIERARCHY_REQUEST_ERR");o.appendChild(t)}return o}function l(e,t,n){var r,o=!(!t||!t.length),i=!!n;o&&(r=new RegExp("^("+t.join("|")+")$"));var a=[];return d(new g(e,!1),function(t){if(!(o&&!r.test(t.nodeType)||i&&!n(t))){var s=e.startContainer;if(t!=s||!L(s)||e.startOffset!=s.length){var c=e.endContainer;t==c&&L(c)&&0==e.endOffset||a.push(t)}}}),a}function h(e){var t="undefined"==typeof e.getName?"Range":e.getName();return"["+t+"("+B.inspectNode(e.startContainer)+":"+e.startOffset+", "+B.inspectNode(e.endContainer)+":"+e.endOffset+")]"}function g(e,t){if(this.range=e,this.clonePartiallySelectedTextNodes=t,!e.collapsed){this.sc=e.startContainer,this.so=e.startOffset,this.ec=e.endContainer,this.eo=e.endOffset;var n=e.commonAncestorContainer;this.sc===this.ec&&L(this.sc)?(this.isSingleCharacterDataNode=!0,this._first=this._last=this._next=this.sc):(this._first=this._next=this.sc!==n||L(this.sc)?V(this.sc,n,!0):this.sc.childNodes[this.so],this._last=this.ec!==n||L(this.ec)?V(this.ec,n,!0):this.ec.childNodes[this.eo-1])}}function p(e){return function(t,n){for(var r,o=n?t:t.parentNode;o;){if(r=o.nodeType,Y(e,r))return o;o=o.parentNode}return null}}function m(e,t){if(rt(e,t))throw new k("INVALID_NODE_TYPE_ERR")}function R(e,t){if(!Y(t,e.nodeType))throw new k("INVALID_NODE_TYPE_ERR")}function v(e,t){if(0>t||t>(L(e)?e.length:e.childNodes.length))throw new k("INDEX_SIZE_ERR")}function C(e,t){if(tt(e,!0)!==tt(t,!0))throw new k("WRONG_DOCUMENT_ERR")}function N(e){if(nt(e,!0))throw new k("NO_MODIFICATION_ALLOWED_ERR")}function E(e,t){if(!e)throw new k(t)}function S(e,t){return t<=(L(e)?e.length:e.childNodes.length)}function y(e){return!!e.startContainer&&!!e.endContainer&&!(G&&(B.isBrokenNode(e.startContainer)||B.isBrokenNode(e.endContainer)))&&Q(e.startContainer)==Q(e.endContainer)&&S(e.startContainer,e.startOffset)&&S(e.endContainer,e.endOffset)}function w(e){if(!y(e))throw new Error("Range error: Range is not valid. This usually happens after DOM mutation. Range: ("+e.inspect()+")")}function T(e,t){w(e);var n=e.startContainer,r=e.startOffset,o=e.endContainer,i=e.endOffset,a=n===o;L(o)&&i>0&&i0&&r=W(n)&&i++,r=0),e.setStartAndEnd(n,r,o,i)}function O(e){w(e);var t=e.commonAncestorContainer.parentNode.cloneNode(!1);return t.appendChild(e.cloneContents()),t.innerHTML}function _(e){e.START_TO_START=dt,e.START_TO_END=ft,e.END_TO_END=ut,e.END_TO_START=lt,e.NODE_BEFORE=ht,e.NODE_AFTER=gt,e.NODE_BEFORE_AND_AFTER=pt,e.NODE_INSIDE=mt}function D(e){_(e),_(e.prototype)}function A(e,t){return function(){w(this);var n,r,o=this.startContainer,a=this.startOffset,s=this.commonAncestorContainer,c=new g(this,!0);o!==s&&(n=V(o,s,!0),r=i(n),o=r.node,a=r.offset),d(c,N),c.reset();var f=e(c);return c.detach(),t(this,o,a,o,a),f}}function x(n,r){function a(e,t){return function(n){R(n,Z),R(Q(n),$);var r=(e?o:i)(n);(t?s:c)(this,r.node,r.offset)}}function s(e,t,n){var o=e.endContainer,i=e.endOffset;(t!==e.startContainer||n!==e.startOffset)&&((Q(t)!=Q(o)||1==z(t,n,o,i))&&(o=t,i=n),r(e,t,n,o,i))}function c(e,t,n){var o=e.startContainer,i=e.startOffset;(t!==e.endContainer||n!==e.endOffset)&&((Q(t)!=Q(o)||-1==z(t,n,o,i))&&(o=t,i=n),r(e,o,i,t,n))}var d=function(){};d.prototype=e.rangePrototype,n.prototype=new d,H.extend(n.prototype,{setStart:function(e,t){m(e,!0),v(e,t),s(this,e,t)},setEnd:function(e,t){m(e,!0),v(e,t),c(this,e,t)},setStartAndEnd:function(){var e=arguments,t=e[0],n=e[1],o=t,i=n;switch(e.length){case 3:i=e[2];break;case 4:o=e[2],i=e[3]}r(this,t,n,o,i)},setBoundary:function(e,t,n){this["set"+(n?"Start":"End")](e,t)},setStartBefore:a(!0,!0),setStartAfter:a(!1,!0),setEndBefore:a(!0,!1),setEndAfter:a(!1,!1),collapse:function(e){w(this),e?r(this,this.startContainer,this.startOffset,this.startContainer,this.startOffset):r(this,this.endContainer,this.endOffset,this.endContainer,this.endOffset)},selectNodeContents:function(e){m(e,!0),r(this,e,0,e,q(e))},selectNode:function(e){m(e,!1),R(e,Z);var t=o(e),n=i(e);r(this,t.node,t.offset,n.node,n.offset)},extractContents:A(u,r),deleteContents:A(f,r),canSurroundContents:function(){w(this),N(this.startContainer),N(this.endContainer);var e=new g(this,!0),n=e._first&&t(e._first,this)||e._last&&t(e._last,this);return e.detach(),!n},splitBoundaries:function(){T(this)},splitBoundariesPreservingPositions:function(e){T(this,e)},normalizeBoundaries:function(){w(this);var e,t=this.startContainer,n=this.startOffset,o=this.endContainer,i=this.endOffset,a=function(e){var t=e.nextSibling;t&&t.nodeType==e.nodeType&&(o=e,i=e.length,e.appendData(t.data),X(t))},s=function(e){var r=e.previousSibling;if(r&&r.nodeType==e.nodeType){t=e;var a=e.length;if(n=r.length,e.insertData(0,r.data),X(r),t==o)i+=n,o=t;else if(o==e.parentNode){var s=W(e);i==s?(o=e,i=a):i>s&&i--}}},c=!0;if(L(o))i==o.length?a(o):0==i&&(e=o.previousSibling,e&&e.nodeType==o.nodeType&&(i=e.length,t==o&&(c=!1),e.appendData(o.data),X(o),o=e));else{if(i>0){var d=o.childNodes[i-1];d&&L(d)&&a(d)}c=!this.collapsed}if(c){if(L(t))0==n?s(t):n==t.length&&(e=t.nextSibling,e&&e.nodeType==t.nodeType&&(o==e&&(o=t,i+=t.length),t.appendData(e.data),X(e)));else if(nx",it=3==ot.firstChild.nodeType}catch(at){}e.features.htmlParsingConforms=it;var st=it?function(e){var t=this.startContainer,n=j(t);if(!t)throw new k("INVALID_STATE_ERR");var r=null;return 1==t.nodeType?r=t:L(t)&&(r=B.parentElement(t)),r=null===r||"HTML"==r.nodeName&&B.isHtmlNamespace(j(r).documentElement)&&B.isHtmlNamespace(r)?n.createElement("body"):r.cloneNode(!1),r.innerHTML=e,B.fragmentFromNodeChildren(r)}:function(e){var t=n(this),r=t.createElement("body");return r.innerHTML=e,B.fragmentFromNodeChildren(r)},ct=["startContainer","startOffset","endContainer","endOffset","collapsed","commonAncestorContainer"],dt=0,ft=1,ut=2,lt=3,ht=0,gt=1,pt=2,mt=3;H.extend(e.rangePrototype,{compareBoundaryPoints:function(e,t){w(this),C(this.startContainer,t.startContainer);var n,r,o,i,a=e==lt||e==dt?"start":"end",s=e==ft||e==dt?"start":"end";return n=this[a+"Container"],r=this[a+"Offset"],o=t[s+"Container"],i=t[s+"Offset"],z(n,r,o,i)},insertNode:function(e){if(w(this),R(e,K),N(this.startContainer),F(e,this.startContainer))throw new k("HIERARCHY_REQUEST_ERR");var t=a(e,this.startContainer,this.startOffset);this.setStartBefore(t)},cloneContents:function(){w(this);var e,t;if(this.collapsed)return n(this).createDocumentFragment();if(this.startContainer===this.endContainer&&L(this.startContainer))return e=this.startContainer.cloneNode(!0),e.data=e.data.slice(this.startOffset,this.endOffset),t=n(this).createDocumentFragment(),t.appendChild(e),t;var r=new g(this,!0);return e=c(r),r.detach(),e},canSurroundContents:function(){w(this),N(this.startContainer),N(this.endContainer);var e=new g(this,!0),n=e._first&&t(e._first,this)||e._last&&t(e._last,this);return e.detach(),!n},surroundContents:function(e){if(R(e,et),!this.canSurroundContents())throw new k("INVALID_STATE_ERR");var t=this.extractContents();if(e.hasChildNodes())for(;e.lastChild;)e.removeChild(e.lastChild);a(e,this.startContainer,this.startOffset),e.appendChild(t),this.selectNode(e)},cloneRange:function(){w(this);for(var e,t=new I(n(this)),r=ct.length;r--;)e=ct[r],t[e]=this[e];return t},toString:function(){w(this);var e=this.startContainer;if(e===this.endContainer&&L(e))return 3==e.nodeType||4==e.nodeType?e.data.slice(this.startOffset,this.endOffset):"";var t=[],n=new g(this,!0);return d(n,function(e){(3==e.nodeType||4==e.nodeType)&&t.push(e.data)}),n.detach(),t.join("")},compareNode:function(e){w(this);var t=e.parentNode,n=W(e);if(!t)throw new k("NOT_FOUND_ERR");var r=this.comparePoint(t,n),o=this.comparePoint(t,n+1);return 0>r?o>0?pt:ht:o>0?gt:mt},comparePoint:function(e,t){return w(this),E(e,"HIERARCHY_REQUEST_ERR"),C(e,this.startContainer),z(e,t,this.startContainer,this.startOffset)<0?-1:z(e,t,this.endContainer,this.endOffset)>0?1:0},createContextualFragment:st,toHtml:function(){return O(this)},intersectsNode:function(e,t){if(w(this),Q(e)!=r(this))return!1;var n=e.parentNode,o=W(e);if(!n)return!0;var i=z(n,o,this.endContainer,this.endOffset),a=z(n,o+1,this.startContainer,this.startOffset);return t?0>=i&&a>=0:0>i&&a>0},isPointInRange:function(e,t){return w(this),E(e,"HIERARCHY_REQUEST_ERR"),C(e,this.startContainer),z(e,t,this.startContainer,this.startOffset)>=0&&z(e,t,this.endContainer,this.endOffset)<=0},intersectsRange:function(e){return s(this,e,!1)},intersectsOrTouchesRange:function(e){return s(this,e,!0)},intersection:function(e){if(this.intersectsRange(e)){var t=z(this.startContainer,this.startOffset,e.startContainer,e.startOffset),n=z(this.endContainer,this.endOffset,e.endContainer,e.endOffset),r=this.cloneRange();return-1==t&&r.setStart(e.startContainer,e.startOffset),1==n&&r.setEnd(e.endContainer,e.endOffset),r}return null},union:function(e){if(this.intersectsOrTouchesRange(e)){var t=this.cloneRange();return-1==z(e.startContainer,e.startOffset,this.startContainer,this.startOffset)&&t.setStart(e.startContainer,e.startOffset),1==z(e.endContainer,e.endOffset,this.endContainer,this.endOffset)&&t.setEnd(e.endContainer,e.endOffset),t}throw new k("Ranges do not intersect")},containsNode:function(e,t){return t?this.intersectsNode(e,!1):this.compareNode(e)==mt},containsNodeContents:function(e){return this.comparePoint(e,0)>=0&&this.comparePoint(e,q(e))<=0},containsRange:function(e){var t=this.intersection(e);return null!==t&&e.equals(t)},containsNodeText:function(e){var t=this.cloneRange();t.selectNode(e);var n=t.getNodes([3]);if(n.length>0){t.setStart(n[0],0);var r=n.pop();return t.setEnd(r,r.length),this.containsRange(t)}return this.containsNodeContents(e)},getNodes:function(e,t){return w(this),l(this,e,t)},getDocument:function(){return n(this)},collapseBefore:function(e){this.setEndBefore(e),this.collapse(!1)},collapseAfter:function(e){this.setStartAfter(e),this.collapse(!0)},getBookmark:function(t){var r=n(this),o=e.createRange(r);t=t||B.getBody(r),o.selectNodeContents(t);var i=this.intersection(o),a=0,s=0;return i&&(o.setEnd(i.startContainer,i.startOffset),a=o.toString().length,s=a+i.toString().length),{start:a,end:s,containerNode:t}},moveToBookmark:function(e){var t=e.containerNode,n=0;this.setStart(t,0),this.collapse(!0);for(var r,o,i,a,s=[t],c=!1,d=!1;!d&&(r=s.pop());)if(3==r.nodeType)o=n+r.length,!c&&e.start>=n&&e.start<=o&&(this.setStart(r,e.start-n),c=!0),c&&e.end>=n&&e.end<=o&&(this.setEnd(r,e.end-n),d=!0),n=o;else for(a=r.childNodes,i=a.length;i--;)s.push(a[i])},getName:function(){return"DomRange"},equals:function(e){return I.rangesEqual(this,e)},isValid:function(){return y(this)},inspect:function(){return h(this)},detach:function(){}}),x(I,P),H.extend(I,{rangeProperties:ct,RangeIterator:g,copyComparisonConstants:D,createPrototypeRange:x,inspect:h,toHtml:O,getRangeDocument:n,rangesEqual:function(e,t){return e.startContainer===t.startContainer&&e.startOffset===t.startOffset&&e.endContainer===t.endContainer&&e.endOffset===t.endOffset}}),e.DomRange=I}),I.createCoreModule("WrappedRange",["DomRange"],function(e,t){var n,r,o=e.dom,i=e.util,a=o.DomPosition,s=e.DomRange,c=o.getBody,d=o.getContentDocument,f=o.isCharacterDataNode;if(e.features.implementsDomRange&&!function(){function r(e){for(var t,n=l.length;n--;)t=l[n],e[t]=e.nativeRange[t];e.collapsed=e.startContainer===e.endContainer&&e.startOffset===e.endOffset}function a(e,t,n,r,o){var i=e.startContainer!==t||e.startOffset!=n,a=e.endContainer!==r||e.endOffset!=o,s=!e.equals(e.nativeRange);(i||a||s)&&(e.setEnd(r,o),e.setStart(t,n))}var f,u,l=s.rangeProperties;n=function(e){if(!e)throw t.createError("WrappedRange: Range must be specified");this.nativeRange=e,r(this)},s.createPrototypeRange(n,a),f=n.prototype,f.selectNode=function(e){this.nativeRange.selectNode(e),r(this)},f.cloneContents=function(){return this.nativeRange.cloneContents()},f.surroundContents=function(e){this.nativeRange.surroundContents(e),r(this)},f.collapse=function(e){this.nativeRange.collapse(e),r(this)},f.cloneRange=function(){return new n(this.nativeRange.cloneRange())},f.refresh=function(){r(this)},f.toString=function(){return this.nativeRange.toString()};var h=document.createTextNode("test");c(document).appendChild(h);var g=document.createRange();g.setStart(h,0),g.setEnd(h,0);try{g.setStart(h,1),f.setStart=function(e,t){this.nativeRange.setStart(e,t),r(this)},f.setEnd=function(e,t){this.nativeRange.setEnd(e,t),r(this)},u=function(e){return function(t){this.nativeRange[e](t),r(this)}}}catch(p){f.setStart=function(e,t){try{this.nativeRange.setStart(e,t)}catch(n){this.nativeRange.setEnd(e,t),this.nativeRange.setStart(e,t)}r(this)},f.setEnd=function(e,t){try{this.nativeRange.setEnd(e,t)}catch(n){this.nativeRange.setStart(e,t),this.nativeRange.setEnd(e,t)}r(this)},u=function(e,t){return function(n){try{this.nativeRange[e](n)}catch(o){this.nativeRange[t](n),this.nativeRange[e](n)}r(this)}}}f.setStartBefore=u("setStartBefore","setEndBefore"),f.setStartAfter=u("setStartAfter","setEndAfter"),f.setEndBefore=u("setEndBefore","setStartBefore"),f.setEndAfter=u("setEndAfter","setStartAfter"),f.selectNodeContents=function(e){this.setStartAndEnd(e,0,o.getNodeLength(e))},g.selectNodeContents(h),g.setEnd(h,3);var m=document.createRange();m.selectNodeContents(h),m.setEnd(h,4),m.setStart(h,2),f.compareBoundaryPoints=-1==g.compareBoundaryPoints(g.START_TO_END,m)&&1==g.compareBoundaryPoints(g.END_TO_START,m)?function(e,t){return t=t.nativeRange||t,e==t.START_TO_END?e=t.END_TO_START:e==t.END_TO_START&&(e=t.START_TO_END),this.nativeRange.compareBoundaryPoints(e,t)}:function(e,t){return this.nativeRange.compareBoundaryPoints(e,t.nativeRange||t)};var R=document.createElement("div");R.innerHTML="123";var v=R.firstChild,C=c(document);C.appendChild(R),g.setStart(v,1),g.setEnd(v,2),g.deleteContents(),"13"==v.data&&(f.deleteContents=function(){this.nativeRange.deleteContents(),r(this)},f.extractContents=function(){var e=this.nativeRange.extractContents();return r(this),e}),C.removeChild(R),C=null,i.isHostMethod(g,"createContextualFragment")&&(f.createContextualFragment=function(e){return this.nativeRange.createContextualFragment(e)}),c(document).removeChild(h),f.getName=function(){return"WrappedRange"},e.WrappedRange=n,e.createNativeRange=function(e){return e=d(e,t,"createNativeRange"),e.createRange()}}(),e.features.implementsTextRange){var u=function(e){var t=e.parentElement(),n=e.duplicate();n.collapse(!0);var r=n.parentElement();n=e.duplicate(),n.collapse(!1);var i=n.parentElement(),a=r==i?r:o.getCommonAncestor(r,i);return a==t?a:o.getCommonAncestor(t,a)},l=function(e){return 0==e.compareEndPoints("StartToEnd",e)},h=function(e,t,n,r,i){var s=e.duplicate();s.collapse(n);var c=s.parentElement();if(o.isOrIsAncestorOf(t,c)||(c=t),!c.canHaveHTML){var d=new a(c.parentNode,o.getNodeIndex(c));return{boundaryPosition:d,nodeInfo:{nodeIndex:d.offset,containerElement:d.node}}}var u=o.getDocument(c).createElement("span");u.parentNode&&o.removeNode(u);for(var l,h,g,p,m,R=n?"StartToStart":"StartToEnd",v=i&&i.containerElement==c?i.nodeIndex:0,C=c.childNodes.length,N=C,E=N;;){if(E==C?c.appendChild(u):c.insertBefore(u,c.childNodes[E]),s.moveToElementText(u),l=s.compareEndPoints(R,e),0==l||v==N)break;if(-1==l){if(N==v+1)break;v=E}else N=N==v+1?v:E;E=Math.floor((v+N)/2),c.removeChild(u)}if(m=u.nextSibling,-1==l&&m&&f(m)){s.setEndPoint(n?"EndToStart":"EndToEnd",e);var S;if(/[\r\n]/.test(m.data)){var y=s.duplicate(),w=y.text.replace(/\r\n/g,"\r").length;for(S=y.moveStart("character",w);-1==(l=y.compareEndPoints("StartToEnd",y));)S++,y.moveStart("character",1)}else S=s.text.length;p=new a(m,S)}else h=(r||!n)&&u.previousSibling,g=(r||n)&&u.nextSibling,p=g&&f(g)?new a(g,0):h&&f(h)?new a(h,h.data.length):new a(c,o.getNodeIndex(u));return o.removeNode(u),{boundaryPosition:p,nodeInfo:{nodeIndex:E,containerElement:c}}},g=function(e,t){var n,r,i,a,s=e.offset,d=o.getDocument(e.node),u=c(d).createTextRange(),l=f(e.node);return l?(n=e.node,r=n.parentNode):(a=e.node.childNodes,n=st;++t)if(!D.isAncestorOf(e[0],e[t]))return!1;return!0}function l(e){var n=e.getNodes();if(!u(n))throw t.createError("getSingleElementFromRange: range "+e.inspect()+" did not consist of a single element");return n[0]}function h(e){return!!e&&"undefined"!=typeof e.text}function g(e,t){var n=new P(t);e._ranges=[n],s(e,n,!1),e.rangeCount=1,e.isCollapsed=n.collapsed}function p(t){if(t._ranges.length=0,"None"==t.docSelection.type)d(t);else{var n=t.docSelection.createRange();if(h(n))g(t,n);else{t.rangeCount=n.length;for(var r,o=k(n.item(0)),i=0;is;++s)a.add(r.item(s));try{a.add(o)}catch(d){throw t.createError("addRange(): Element within the specified Range could not be added to control selection (does it have layout?)")}a.select(),p(e)}function R(e,t,n){this.nativeSelection=e,this.docSelection=t,this._ranges=[],this.win=n,this.refresh()}function v(e){e.win=e.anchorNode=e.focusNode=e._ranges=null,e.rangeCount=e.anchorOffset=e.focusOffset=0,e.detached=!0}function C(e,t){for(var n,r,o=tt.length;o--;)if(n=tt[o],r=n.selection,"deleteAll"==t)v(r);else if(n.win==e)return"delete"==t?(tt.splice(o,1),!0):r;return"deleteAll"==t&&(tt.length=0),null}function N(e,n){for(var r,o=k(n[0].startContainer),i=L(o).createControlRange(),a=0,s=n.length;s>a;++a){r=l(n[a]);try{i.add(r)}catch(c){throw t.createError("setRanges(): Element within one of the specified Ranges could not be added to control selection (does it have layout?)")}}i.select(),p(e)}function E(e,t){if(e.win.document!=k(t))throw new I("WRONG_DOCUMENT_ERR")}function S(t){return function(n,r){var o;this.rangeCount?(o=this.getRangeAt(0),o["set"+(t?"Start":"End")](n,r)):(o=e.createRange(this.win.document),o.setStartAndEnd(n,r)),this.setSingleRange(o,this.isBackward())}}function y(e){var t=[],n=new B(e.anchorNode,e.anchorOffset),r=new B(e.focusNode,e.focusOffset),o="function"==typeof e.getName?e.getName():"Selection";if("undefined"!=typeof e.rangeCount)for(var i=0,a=e.rangeCount;a>i;++i)t[i]=b.inspect(e.getRangeAt(i));return"["+o+"(Ranges: "+t.join(", ")+")(anchor: "+n.inspect()+", focus: "+r.inspect()+"]"}e.config.checkSelectionRanges=!0;var w,T,O="boolean",_="number",D=e.dom,A=e.util,x=A.isHostMethod,b=e.DomRange,P=e.WrappedRange,I=e.DOMException,B=D.DomPosition,H=e.features,M="Control",k=D.getDocument,L=D.getBody,W=b.rangesEqual,F=x(window,"getSelection"),j=A.isHostObject(document,"selection");H.implementsWinGetSelection=F,H.implementsDocSelection=j;var z=j&&(!F||e.config.preferTextRange);if(z)w=i,e.isSelectionValid=function(e){var t=r(e,"isSelectionValid").document,n=t.selection;return"None"!=n.type||k(n.createRange().parentElement())==t};else{if(!F)return t.fail("Neither document.selection or window.getSelection() detected."),!1;w=o,e.isSelectionValid=function(){return!0}}e.getNativeSelection=w;var U=w();if(!U)return t.fail("Native selection was null (possibly issue 138?)"),!1;var V=e.createNativeRange(document),q=L(document),Y=A.areHostProperties(U,["anchorNode","focusNode","anchorOffset","focusOffset"]);H.selectionHasAnchorAndFocus=Y;var Q=x(U,"extend");H.selectionHasExtend=Q;var G=typeof U.rangeCount==_;H.selectionHasRangeCount=G;var X=!1,Z=!0,$=Q?function(t,n){var r=b.getRangeDocument(n),o=e.createRange(r);o.collapseToPoint(n.endContainer,n.endOffset),t.addRange(f(o)),t.extend(n.startContainer,n.startOffset)}:null;A.areHostMethods(U,["addRange","getRangeAt","removeAllRanges"])&&typeof U.rangeCount==_&&H.implementsDomRange&&!function(){var t=window.getSelection();if(t){for(var n=t.rangeCount,r=n>1,o=[],i=a(t),s=0;n>s;++s)o[s]=t.getRangeAt(s);var c=D.createTestElement(document,"",!1),d=c.appendChild(document.createTextNode("   ")),f=document.createRange();if(f.setStart(d,1),f.collapse(!0),t.removeAllRanges(),t.addRange(f),Z=1==t.rangeCount,t.removeAllRanges(),!r){var u=window.navigator.appVersion.match(/Chrome\/(.*?) /);if(u&&parseInt(u[1])>=36)X=!1;else{var l=f.cloneRange();f.setStart(d,0),l.setEnd(d,3),l.setStart(d,2),t.addRange(f),t.addRange(l),X=2==t.rangeCount}}for(D.removeNode(c),t.removeAllRanges(),s=0;n>s;++s)0==s&&i?$?$(t,o[s]):(e.warn("Rangy initialization: original selection was backwards but selection has been restored forwards because the browser does not support Selection.extend"),t.addRange(o[s])):t.addRange(o[s])}}(),H.selectionSupportsMultipleRanges=X,H.collapsedNonEditableSelectionsSupported=Z;var J,K=!1;q&&x(q,"createControlRange")&&(J=q.createControlRange(),A.areHostProperties(J,["item","add"])&&(K=!0)),H.implementsControlRange=K,T=Y?function(e){return e.anchorNode===e.focusNode&&e.anchorOffset===e.focusOffset}:function(e){return e.rangeCount?e.getRangeAt(e.rangeCount-1).collapsed:!1};var et;x(U,"getRangeAt")?et=function(e,t){try{return e.getRangeAt(t)}catch(n){return null}}:Y&&(et=function(t){var n=k(t.anchorNode),r=e.createRange(n);return r.setStartAndEnd(t.anchorNode,t.anchorOffset,t.focusNode,t.focusOffset),r.collapsed!==this.isCollapsed&&r.setStartAndEnd(t.focusNode,t.focusOffset,t.anchorNode,t.anchorOffset),r}),R.prototype=e.selectionPrototype;var tt=[],nt=function(e){if(e&&e instanceof R)return e.refresh(),e;e=r(e,"getNativeSelection");var t=C(e),n=w(e),o=j?i(e):null;return t?(t.nativeSelection=n,t.docSelection=o,t.refresh()):(t=new R(n,o,e),tt.push({win:e,selection:t})),t};e.getSelection=nt,A.createAliasForDeprecatedMethod(e,"getIframeSelection","getSelection");var rt=R.prototype;if(!z&&Y&&A.areHostMethods(U,["removeAllRanges","addRange"])){rt.removeAllRanges=function(){this.nativeSelection.removeAllRanges(),d(this)};var ot=function(e,t){$(e.nativeSelection,t),e.refresh()};rt.addRange=G?function(t,r){if(K&&j&&this.docSelection.type==M)m(this,t);else if(n(r)&&Q)ot(this,t);else{var o;X?o=this.rangeCount:(this.removeAllRanges(),o=0);var i=f(t).cloneRange();try{this.nativeSelection.addRange(i)}catch(a){}if(this.rangeCount=this.nativeSelection.rangeCount,this.rangeCount==o+1){if(e.config.checkSelectionRanges){var c=et(this.nativeSelection,this.rangeCount-1);c&&!W(c,t)&&(t=new P(c))}this._ranges[this.rangeCount-1]=t,s(this,t,st(this.nativeSelection)),this.isCollapsed=T(this)}else this.refresh()}}:function(e,t){n(t)&&Q?ot(this,e):(this.nativeSelection.addRange(f(e)),this.refresh())},rt.setRanges=function(e){if(K&&j&&e.length>1)N(this,e);else{this.removeAllRanges();for(var t=0,n=e.length;n>t;++t)this.addRange(e[t])}}}else{if(!(x(U,"empty")&&x(V,"select")&&K&&z))return t.fail("No means of selecting a Range or TextRange was found"),!1;rt.removeAllRanges=function(){try{if(this.docSelection.empty(),"None"!=this.docSelection.type){var e;if(this.anchorNode)e=k(this.anchorNode);else if(this.docSelection.type==M){var t=this.docSelection.createRange();t.length&&(e=k(t.item(0)))}if(e){var n=L(e).createTextRange();n.select(),this.docSelection.empty()}}}catch(r){}d(this)},rt.addRange=function(t){this.docSelection.type==M?m(this,t):(e.WrappedTextRange.rangeToTextRange(t).select(),this._ranges[0]=t,this.rangeCount=1,this.isCollapsed=this._ranges[0].collapsed,s(this,t,!1))},rt.setRanges=function(e){this.removeAllRanges();var t=e.length;t>1?N(this,e):t&&this.addRange(e[0])}}rt.getRangeAt=function(e){if(0>e||e>=this.rangeCount)throw new I("INDEX_SIZE_ERR");return this._ranges[e].cloneRange()};var it;if(z)it=function(t){var n;e.isSelectionValid(t.win)?n=t.docSelection.createRange():(n=L(t.win.document).createTextRange(),n.collapse(!0)),t.docSelection.type==M?p(t):h(n)?g(t,n):d(t)};else if(x(U,"getRangeAt")&&typeof U.rangeCount==_)it=function(t){if(K&&j&&t.docSelection.type==M)p(t);else if(t._ranges.length=t.rangeCount=t.nativeSelection.rangeCount,t.rangeCount){for(var n=0,r=t.rangeCount;r>n;++n)t._ranges[n]=new e.WrappedRange(t.nativeSelection.getRangeAt(n));s(t,t._ranges[t.rangeCount-1],st(t.nativeSelection)),t.isCollapsed=T(t)}else d(t)};else{if(!Y||typeof U.isCollapsed!=O||typeof V.collapsed!=O||!H.implementsDomRange)return t.fail("No means of obtaining a Range or TextRange from the user's selection was found"),!1;it=function(e){var t,n=e.nativeSelection;n.anchorNode?(t=et(n,0),e._ranges=[t],e.rangeCount=1,c(e),e.isCollapsed=T(e)):d(e)}}rt.refresh=function(e){var t=e?this._ranges.slice(0):null,n=this.anchorNode,r=this.anchorOffset;if(it(this),e){var o=t.length;if(o!=this._ranges.length)return!0;if(this.anchorNode!=n||this.anchorOffset!=r)return!0;for(;o--;)if(!W(t[o],this._ranges[o]))return!0;return!1}};var at=function(e,t){var n=e.getAllRanges();e.removeAllRanges();for(var r=0,o=n.length;o>r;++r)W(t,n[r])||e.addRange(n[r]);e.rangeCount||d(e)};rt.removeRange=K&&j?function(e){if(this.docSelection.type==M){for(var t,n=this.docSelection.createRange(),r=l(e),o=k(n.item(0)),i=L(o).createControlRange(),a=!1,s=0,c=n.length;c>s;++s)t=n.item(s),t!==r||a?i.add(n.item(s)):a=!0;i.select(),p(this)}else at(this,e)}:function(e){at(this,e)};var st;!z&&Y&&H.implementsDomRange?(st=a,rt.isBackward=function(){return st(this)}):st=rt.isBackward=function(){return!1},rt.isBackwards=rt.isBackward,rt.toString=function(){for(var e=[],t=0,n=this.rangeCount;n>t;++t)e[t]=""+this._ranges[t];return e.join("")},rt.collapse=function(t,n){E(this,t);var r=e.createRange(t);r.collapseToPoint(t,n),this.setSingleRange(r),this.isCollapsed=!0},rt.collapseToStart=function(){if(!this.rangeCount)throw new I("INVALID_STATE_ERR");var e=this._ranges[0];this.collapse(e.startContainer,e.startOffset)},rt.collapseToEnd=function(){if(!this.rangeCount)throw new I("INVALID_STATE_ERR");var e=this._ranges[this.rangeCount-1];this.collapse(e.endContainer,e.endOffset)},rt.selectAllChildren=function(t){E(this,t);var n=e.createRange(t);n.selectNodeContents(t),this.setSingleRange(n)},rt.deleteFromDocument=function(){if(K&&j&&this.docSelection.type==M){for(var e,t=this.docSelection.createRange();t.length;)e=t.item(0),t.remove(e),D.removeNode(e);this.refresh()}else if(this.rangeCount){var n=this.getAllRanges();if(n.length){this.removeAllRanges();for(var r=0,o=n.length;o>r;++r)n[r].deleteContents();this.addRange(n[o-1])}}},rt.eachRange=function(e,t){for(var n=0,r=this._ranges.length;r>n;++n)if(e(this.getRangeAt(n)))return t},rt.getAllRanges=function(){var e=[];return this.eachRange(function(t){e.push(t)}),e},rt.setSingleRange=function(e,t){this.removeAllRanges(),this.addRange(e,t)},rt.callMethodOnEachRange=function(e,t){var n=[];return this.eachRange(function(r){n.push(r[e].apply(r,t||[]))}),n},rt.setStart=S(!0),rt.setEnd=S(!1),e.rangePrototype.select=function(e){nt(this.getDocument()).setSingleRange(this,e)},rt.changeEachRange=function(e){var t=[],n=this.isBackward();this.eachRange(function(n){e(n),t.push(n)}),this.removeAllRanges(),n&&1==t.length?this.addRange(t[0],"backward"):this.setRanges(t)},rt.containsNode=function(e,t){return this.eachRange(function(n){return n.containsNode(e,t)},!0)||!1},rt.getBookmark=function(e){return{backward:this.isBackward(),rangeBookmarks:this.callMethodOnEachRange("getBookmark",[e])}},rt.moveToBookmark=function(t){for(var n,r,o=[],i=0;n=t.rangeBookmarks[i++];)r=e.createRange(this.win),r.moveToBookmark(n),o.push(r);t.backward?this.setSingleRange(o[0],"backward"):this.setRanges(o)},rt.saveRanges=function(){return{backward:this.isBackward(),ranges:this.callMethodOnEachRange("cloneRange")}},rt.restoreRanges=function(e){this.removeAllRanges();for(var t,n=0;t=e.ranges[n];++n)this.addRange(t,e.backward&&0==n)},rt.toHtml=function(){var e=[];return this.eachRange(function(t){e.push(b.toHtml(t))}),e.join("")},H.implementsTextRange&&(rt.getNativeTextRange=function(){var n;if(n=this.docSelection){var r=n.createRange();if(h(r))return r;throw t.createError("getNativeTextRange: selection is a control selection")}if(this.rangeCount>0)return e.WrappedTextRange.rangeToTextRange(this.getRangeAt(0));throw t.createError("getNativeTextRange: selection contains no range")}),rt.getName=function(){return"WrappedSelection"},rt.inspect=function(){return y(this)},rt.detach=function(){C(this.win,"delete"),v(this)},R.detachAll=function(){C(null,"deleteAll")},R.inspect=y,R.isDirectionBackward=n,e.Selection=R,e.selectionPrototype=rt,e.addShimListener(function(e){"undefined"==typeof e.getSelection&&(e.getSelection=function(){return nt(e)}),e=null})});var L=!1,W=function(){L||(L=!0,!I.initialized&&I.config.autoInitialize&&u())};return b&&("complete"==document.readyState?W():(e(document,"addEventListener")&&document.addEventListener("DOMContentLoaded",W,!1),H(window,"load",W))),I},this); \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index e9bf94e4..fbb90294 100644 --- a/templates/index.html +++ b/templates/index.html @@ -9,6 +9,7 @@ + @@ -81,7 +82,7 @@
-

...

+

...

From 3b19d4518aedbc28be5e37bfae845cb9a9c3b3bd Mon Sep 17 00:00:00 2001 From: Gnome Ann <> Date: Mon, 27 Sep 2021 15:57:07 -0400 Subject: [PATCH 05/33] Remove "Desktop only" from application.js comments --- static/application.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/static/application.js b/static/application.js index 197c0494..a2a8967d 100644 --- a/static/application.js +++ b/static/application.js @@ -907,7 +907,7 @@ function highlightEditingChunks() { } } -// (Desktop client only) This gets run every time the text in a chunk is edited +// This gets run every time the text in a chunk is edited // or a chunk is deleted function chunkOnDOMMutate(mutations, observer) { if(!gametext_bound) { @@ -938,7 +938,7 @@ function chunkOnPaste(event) { } } -// (Desktop client only) This gets run every time the caret moves in the editor +// This gets run every time the caret moves in the editor function chunkOnSelectionChange(event) { if(!gametext_bound) { return; @@ -951,7 +951,7 @@ function chunkOnSelectionChange(event) { }, 2); } -// (Desktop client only) This gets run when you defocus the editor by clicking +// This gets run when you defocus the editor by clicking // outside of the editor or by pressing escape or tab function chunkOnFocusOut(event) { if(!gametext_bound || event.target !== gametext) { @@ -1347,7 +1347,7 @@ $(document).ready(function(){ connect_status.addClass("color_orange"); }); - // Register editing events (desktop) + // Register editing events $(gametext).on('keydown', chunkOnKeyDown ).on('paste', From 5f154b8b55adcf5d506ead309231dcbd971814a2 Mon Sep 17 00:00:00 2001 From: Gnome Ann <> Date: Mon, 27 Sep 2021 16:04:42 -0400 Subject: [PATCH 06/33] Replace all references to #gametext with game_text Come to think of it, I'm not sure where the variable "gametext" was even defined. Is this some kind of sorcery? --- static/application.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/static/application.js b/static/application.js index a2a8967d..f78a2043 100644 --- a/static/application.js +++ b/static/application.js @@ -954,16 +954,16 @@ function chunkOnSelectionChange(event) { // This gets run when you defocus the editor by clicking // outside of the editor or by pressing escape or tab function chunkOnFocusOut(event) { - if(!gametext_bound || event.target !== gametext) { + if(!gametext_bound || event.target !== game_text[0]) { return; } setTimeout(function() { - if(document.activeElement === gametext || gametext.contains(document.activeElement)) { + if(document.activeElement === game_text[0] || game_text[0].contains(document.activeElement)) { return; } syncAllModifiedChunks(true); setTimeout(function() { - var blurred = $("#gametext")[0] !== document.activeElement; + var blurred = game_text[0] !== document.activeElement; if(blurred) { deleteEmptyChunks(); } @@ -975,7 +975,7 @@ function chunkOnFocusOut(event) { } function bindGametext() { - mutation_observer.observe($("#gametext")[0], {characterData: true, childList: true, subtree: true}); + mutation_observer.observe(game_text[0], {characterData: true, childList: true, subtree: true}); gametext_bound = true; } @@ -1071,7 +1071,7 @@ $(document).ready(function(){ $('#allowediting').prop('checked', allowedit).prop('disabled', false).change().off('change').on('change', function () { if(allowtoggle) { allowedit = $(this).prop('checked'); - $(gametext).attr('contenteditable', allowedit); + game_text.attr('contenteditable', allowedit); } }); } else if(msg.cmd == "updatescreen") { @@ -1348,7 +1348,7 @@ $(document).ready(function(){ }); // Register editing events - $(gametext).on('keydown', + game_text.on('keydown', chunkOnKeyDown ).on('paste', chunkOnPaste @@ -1365,8 +1365,8 @@ $(document).ready(function(){ // because the gods of HTML and JavaScript say so $(document.body).on('focusin', function(event) { setTimeout(function() { - if(document.activeElement !== gametext && gametext.contains(document.activeElement)) { - gametext.focus(); + if(document.activeElement !== game_text[0] && game_text[0].contains(document.activeElement)) { + game_text[0].focus(); } }, 2); }); From 25758dc3ab2dd2cfbd5d3d11c0b7251b08fcf9c4 Mon Sep 17 00:00:00 2001 From: Gnome Ann <> Date: Mon, 27 Sep 2021 16:33:24 -0400 Subject: [PATCH 07/33] Fix event handler for clicking on story text It wouldn't trigger any events originally when you click on parts of the story text area that didn't contain any text, e.g. on a blank line or on the blank part of a line to the right of the actual text. --- static/application.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/application.js b/static/application.js index f78a2043..2410894c 100644 --- a/static/application.js +++ b/static/application.js @@ -1352,7 +1352,7 @@ $(document).ready(function(){ chunkOnKeyDown ).on('paste', chunkOnPaste - ).on('focus', + ).on('click', chunkOnSelectionChange ).on('keydown', chunkOnSelectionChange From 47c6f48e9456dd2687764484b5e1d6b7c968b35e Mon Sep 17 00:00:00 2001 From: Gnome Ann <> Date: Mon, 27 Sep 2021 16:37:38 -0400 Subject: [PATCH 08/33] Move padding and overflow-y from #gamescreen to #gametext For stories that are long enough for the scroll bar to appear on the screen, Firefox on desktop would originally only allow you to start editing if you click on the actual text, i.e. you couldn't click on the blank part of a line. This behaviour is now fixed. --- static/application.js | 2 +- static/custom.css | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/static/application.js b/static/application.js index 2410894c..e9156158 100644 --- a/static/application.js +++ b/static/application.js @@ -402,7 +402,7 @@ function hideWaitAnimation() { function scrollToBottom() { setTimeout(function () { - $('#gamescreen').animate({scrollTop: $('#gamescreen').prop('scrollHeight')}, 500); + game_text.animate({scrollTop: game_text.prop('scrollHeight')}, 500); }, 5); } diff --git a/static/custom.css b/static/custom.css index 634a2eb8..1f186696 100644 --- a/static/custom.css +++ b/static/custom.css @@ -69,10 +69,8 @@ chunk.editing, chunk.editing * { #gamescreen { height: 490px; margin-top: 10px; - padding: 10px; display: flex; vertical-align: bottom; - overflow-y: scroll; background-color: #262626; color: #ffffff; font-size: 12pt; @@ -87,6 +85,8 @@ chunk.editing, chunk.editing * { max-height: 100%; width: 100%; word-wrap: break-word; + padding: 10px; + overflow-y: scroll; } #seqselmenu { From a327eed2c3fd47eefe3be9e284a54d5a4dfc31bb Mon Sep 17 00:00:00 2001 From: Gnome Ann <> Date: Mon, 27 Sep 2021 17:44:22 -0400 Subject: [PATCH 09/33] Fix editor scrolling issues --- aiserver.py | 2 +- static/application.js | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/aiserver.py b/aiserver.py index d5f3fd86..dd7bd435 100644 --- a/aiserver.py +++ b/aiserver.py @@ -1476,7 +1476,7 @@ def update_story_chunk(idx: Union[int, str]): item = vars.acregex_ui.sub('\\1', item) # Add special formatting to adventure actions chunk_text = f'{formatforhtml(item)}' - emit('from_server', {'cmd': 'updatechunk', 'data': {'index': idx, 'html': chunk_text, 'last': (idx == (vars.actions.get_last_key() if len(vars.actions) else 0))}}, broadcast=True) + emit('from_server', {'cmd': 'updatechunk', 'data': {'index': idx, 'html': chunk_text}}, broadcast=True) #==================================================================# diff --git a/static/application.js b/static/application.js index e9156158..8eb3bc5d 100644 --- a/static/application.js +++ b/static/application.js @@ -947,6 +947,12 @@ function chunkOnSelectionChange(event) { syncAllModifiedChunks(); setTimeout(function() { highlightEditingChunks(); + // Attempt to prevent Chromium-based browsers on Android from + // scrolling away from the current selection + setTimeout(function() { + game_text.blur(); + game_text.focus(); + }, 144); }, 2); }, 2); } @@ -1096,7 +1102,7 @@ $(document).ready(function(){ scrollToBottom(); } else if(msg.cmd == "updatechunk") { hideMessage(); - const {index, html, last} = msg.data; + const {index, html} = msg.data; const existingChunk = game_text.children(`#n${index}`) const newChunk = $(html); unbindGametext(); @@ -1110,10 +1116,6 @@ $(document).ready(function(){ } bindGametext(); hide([$('#curtain')]); - if(last) { - // Scroll to bottom of text if it's the last element - scrollToBottom(); - } } else if(msg.cmd == "removechunk") { hideMessage(); let index = msg.data; From 09d540b580fa63f1cea9a4fb60c82bec93bf4915 Mon Sep 17 00:00:00 2001 From: Gnome Ann <> Date: Mon, 27 Sep 2021 17:55:54 -0400 Subject: [PATCH 10/33] Prevent editing the "Welcome to KoboldAI!" message --- static/application.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/static/application.js b/static/application.js index 8eb3bc5d..6aea6735 100644 --- a/static/application.js +++ b/static/application.js @@ -1088,6 +1088,8 @@ $(document).ready(function(){ changemode(); } unbindGametext(); + allowedit = gamestarted && $("#allowediting").prop('checked'); + game_text.attr('contenteditable', allowedit); modified_chunks = new Set(); empty_chunks = new Set(); game_text.html(msg.data); From fff095b496b0480e48d90e238497bf2736a84f3c Mon Sep 17 00:00:00 2001 From: Gnome Ann <> Date: Mon, 27 Sep 2021 18:10:33 -0400 Subject: [PATCH 11/33] Stop all animations before performing scroll-to-bottom So that if the animation is triggered multiple times you don't have to wait for all the animations to be performed one after the other before you can finally manually scroll. --- static/application.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/application.js b/static/application.js index 6aea6735..89c8bf7d 100644 --- a/static/application.js +++ b/static/application.js @@ -402,7 +402,7 @@ function hideWaitAnimation() { function scrollToBottom() { setTimeout(function () { - game_text.animate({scrollTop: game_text.prop('scrollHeight')}, 500); + game_text.stop(true).animate({scrollTop: game_text.prop('scrollHeight')}, 500); }, 5); } From e29e7b11ec8cc80f5dd5462e63e4fa418bad1809 Mon Sep 17 00:00:00 2001 From: Gnome Ann <> Date: Mon, 27 Sep 2021 18:12:15 -0400 Subject: [PATCH 12/33] Bump version number to 1.16.1 --- aiserver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aiserver.py b/aiserver.py index dd7bd435..beb2aa4d 100644 --- a/aiserver.py +++ b/aiserver.py @@ -1,6 +1,6 @@ #==================================================================# # KoboldAI -# Version: 1.16.0 +# Version: 1.16.1 # By: KoboldAIDev and the KoboldAI Community #==================================================================# From 13b81c7523401dbe4e07b0c09fc2b9963b5fa5f5 Mon Sep 17 00:00:00 2001 From: Gnome Ann <> Date: Mon, 27 Sep 2021 22:21:14 -0400 Subject: [PATCH 13/33] Prevent the user from deleting the prompt --- aiserver.py | 2 ++ static/application.js | 43 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/aiserver.py b/aiserver.py index beb2aa4d..1a143ece 100644 --- a/aiserver.py +++ b/aiserver.py @@ -1578,6 +1578,8 @@ def deleterequest(): def inlineedit(chunk, data): chunk = int(chunk) if(chunk == 0): + if(len(data.strip()) == 0): + return vars.prompt = data else: vars.actions[chunk-1] = data diff --git a/static/application.js b/static/application.js index 89c8bf7d..7ac59323 100644 --- a/static/application.js +++ b/static/application.js @@ -73,6 +73,7 @@ var modified_chunks = new Set(); var empty_chunks = new Set(); var mutation_observer = null; var gametext_bound = false; +var saved_prompt = "..."; var sman_allow_delete = false; var sman_allow_rename = false; @@ -844,7 +845,8 @@ function applyChunkDeltas(nodes) { var selected_chunks = buildChunkSetFromNodeArray(getSelectedNodes()); for(var i = 0; i < chunks.length; i++) { var chunk = document.getElementById("n" + chunks[i]); - if(chunk && chunk.innerText.length != 0) { + if(chunk && chunk.innerText.length != 0 && chunks[i] != '0') { + console.log(chunks[i]) if(!selected_chunks.has(chunks[i])) { modified_chunks.delete(chunks[i]); socket.send({'cmd': 'inlineedit', 'chunk': chunks[i], 'data': chunk.innerText.replace(/\u00a0/g, " ")}); @@ -879,12 +881,46 @@ function syncAllModifiedChunks(including_selected_chunks=false) { } } +function restorePrompt() { + if($("#gametext > chunk").length == 0 && game_text[0].innerText.trim().length) { + saved_prompt = game_text[0].innerText.replace(/\u00a0/g, " "); + unbindGametext(); + game_text[0].innerText = ""; + bindGametext(); + } + if($("#n0").length) { + $("#n0").remove(); + } + var prompt_chunk = document.createElement("chunk"); + prompt_chunk.setAttribute("n", "0"); + prompt_chunk.setAttribute("id", "n0"); + prompt_chunk.setAttribute("tabindex", "-1"); + prompt_chunk.innerText = saved_prompt; + unbindGametext(); + game_text[0].prepend(prompt_chunk); + bindGametext(); + modified_chunks.delete('0'); + empty_chunks.delete('0'); + socket.send({'cmd': 'inlineedit', 'chunk': '0', 'data': saved_prompt}); +} + function deleteEmptyChunks() { var chunks = Array.from(empty_chunks); for(var i = 0; i < chunks.length; i++) { empty_chunks.delete(chunks[i]); - socket.send({'cmd': 'inlinedelete', 'data': chunks[i]}); + if(chunks[i] === "0") { + // Don't delete the prompt + restorePrompt(); + } else { + socket.send({'cmd': 'inlinedelete', 'data': chunks[i]}); + } } + if(modified_chunks.has('0')) { + modified_chunks.delete(chunks[i]); + socket.send({'cmd': 'inlineedit', 'chunk': chunks[i], 'data': document.getElementById("n0").innerText.replace(/\u00a0/g, " ")}); + } + console.log(empty_chunks) + saved_prompt = $("#n0")[0].innerText.replace(/\u00a0/g, " "); } function highlightEditingChunks() { @@ -1094,6 +1130,9 @@ $(document).ready(function(){ empty_chunks = new Set(); game_text.html(msg.data); bindGametext(); + if(gamestarted) { + saved_prompt = $("#n0")[0].innerText.replace(/\u00a0/g, " "); + } // Scroll to bottom of text if(newly_loaded) { scrollToBottom(); From 431acce0bb5ac1aa52e6f5bdd63d35804b08a8fd Mon Sep 17 00:00:00 2001 From: henk717 Date: Tue, 28 Sep 2021 04:44:17 +0200 Subject: [PATCH 14/33] Remove unneeded scrollbars There was an unneeded scrollbar growing as you were expanding towards the top of the screen, now this scrollbar is hidden until you actually need it making the UI look a bit more polished. --- static/custom.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/custom.css b/static/custom.css index 1f186696..fa9be0b4 100644 --- a/static/custom.css +++ b/static/custom.css @@ -86,7 +86,7 @@ chunk.editing, chunk.editing * { width: 100%; word-wrap: break-word; padding: 10px; - overflow-y: scroll; + overflow-y: auto; } #seqselmenu { From b39f12b60b2383d5c297a4cefd5160a97f5cff2c Mon Sep 17 00:00:00 2001 From: Gnome Ann <> Date: Mon, 27 Sep 2021 22:47:27 -0400 Subject: [PATCH 15/33] Remove unneeded console.log() --- static/application.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/static/application.js b/static/application.js index 7ac59323..2b345a1c 100644 --- a/static/application.js +++ b/static/application.js @@ -846,7 +846,6 @@ function applyChunkDeltas(nodes) { for(var i = 0; i < chunks.length; i++) { var chunk = document.getElementById("n" + chunks[i]); if(chunk && chunk.innerText.length != 0 && chunks[i] != '0') { - console.log(chunks[i]) if(!selected_chunks.has(chunks[i])) { modified_chunks.delete(chunks[i]); socket.send({'cmd': 'inlineedit', 'chunk': chunks[i], 'data': chunk.innerText.replace(/\u00a0/g, " ")}); @@ -919,7 +918,6 @@ function deleteEmptyChunks() { modified_chunks.delete(chunks[i]); socket.send({'cmd': 'inlineedit', 'chunk': chunks[i], 'data': document.getElementById("n0").innerText.replace(/\u00a0/g, " ")}); } - console.log(empty_chunks) saved_prompt = $("#n0")[0].innerText.replace(/\u00a0/g, " "); } From 3297c27fcdaba0b45659379c8254007ac62321c2 Mon Sep 17 00:00:00 2001 From: Gnome Ann <> Date: Mon, 27 Sep 2021 22:56:12 -0400 Subject: [PATCH 16/33] Fix using escape to save edits --- static/application.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/static/application.js b/static/application.js index 2b345a1c..7c64b116 100644 --- a/static/application.js +++ b/static/application.js @@ -74,6 +74,7 @@ var empty_chunks = new Set(); var mutation_observer = null; var gametext_bound = false; var saved_prompt = "..."; +var override_focusout = false; var sman_allow_delete = false; var sman_allow_rename = false; @@ -733,9 +734,8 @@ function chunkOnKeyDown(event) { // Make escape commit the changes (Originally we had Enter here to but its not required and nicer for users if we let them type freely // You can add the following after 27 if you want it back to committing on enter : || (!event.shiftKey && event.keyCode == 13) if(event.keyCode == 27) { - setTimeout(function () { - event.target.blur(); - }, 5); + override_focusout = true; + game_text.blur(); event.preventDefault(); return; } @@ -974,7 +974,8 @@ function chunkOnPaste(event) { // This gets run every time the caret moves in the editor function chunkOnSelectionChange(event) { - if(!gametext_bound) { + if(!gametext_bound || override_focusout) { + override_focusout = false; return; } setTimeout(function() { From e8cefcc2c65d8d290919333bf23ce737733fc572 Mon Sep 17 00:00:00 2001 From: Gnome Ann <> Date: Mon, 27 Sep 2021 23:10:34 -0400 Subject: [PATCH 17/33] (Firefox) Fix certain chunks not updating when editing I don't know why Firefox is so weird but we need to do this or else some chunks don't update properly when you edit multiple chunks at the same time. One example of when this happened is when you have a story with at least one chunk other than the prompt. Then, if you select the entire story except for the first few characters of the prompt and then delete the selected characters, and then defocus the story to save your changes, the last chunk in the story will not register as having been deleted, which you can verify if you refresh the page. Another example. If your story has a chunk with no trailing newlines followed by a chunk with exactly two leading newlines, if you delete the first newline from the latter chunk and then defocus to save your edits, the newline will be there again when you refresh the page. --- static/application.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/static/application.js b/static/application.js index 7c64b116..04a5a45b 100644 --- a/static/application.js +++ b/static/application.js @@ -950,11 +950,8 @@ function chunkOnDOMMutate(mutations, observer) { var nodes = []; for(var i = 0; i < mutations.length; i++) { var mutation = mutations[i]; - if(mutation.type === "childList") { - nodes = nodes.concat(Array.from(mutation.addedNodes), Array.from(mutation.removedNodes)); - } else { - nodes.push(mutation.target); - } + nodes = nodes.concat(Array.from(mutation.addedNodes), Array.from(mutation.removedNodes)); + nodes.push(mutation.target); } applyChunkDeltas(nodes); } From f632ad988083cdfbf23748f71e443dbce3e713be Mon Sep 17 00:00:00 2001 From: Gnome Ann <> Date: Mon, 27 Sep 2021 23:38:41 -0400 Subject: [PATCH 18/33] Use a more robust scheme for restorePrompt The original way didn't work properly in Firefox on Android when you select all text and replace it with something else. --- static/application.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/static/application.js b/static/application.js index 04a5a45b..b696e85c 100644 --- a/static/application.js +++ b/static/application.js @@ -881,8 +881,8 @@ function syncAllModifiedChunks(including_selected_chunks=false) { } function restorePrompt() { - if($("#gametext > chunk").length == 0 && game_text[0].innerText.trim().length) { - saved_prompt = game_text[0].innerText.replace(/\u00a0/g, " "); + if(game_text[0].firstChild.nodeType === 3) { + saved_prompt = game_text[0].firstChild.textContent.replace(/\u00a0/g, " "); unbindGametext(); game_text[0].innerText = ""; bindGametext(); From 231290608d102958428176aad3dd4dfbc6742bf7 Mon Sep 17 00:00:00 2001 From: Gnome Ann <> Date: Tue, 28 Sep 2021 00:48:37 -0400 Subject: [PATCH 19/33] Do a better job of preventing editing of text when required --- aiserver.py | 2 +- static/application.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/aiserver.py b/aiserver.py index 1a143ece..d37eb810 100644 --- a/aiserver.py +++ b/aiserver.py @@ -956,7 +956,7 @@ def actionsubmit(data, actionmode=0): vars.prompt = data if(not vars.noai): # Clear the startup text from game screen - emit('from_server', {'cmd': 'updatescreen', 'gamestarted': vars.gamestarted, 'data': 'Please wait, generating story...'}, broadcast=True) + emit('from_server', {'cmd': 'updatescreen', 'gamestarted': False, 'data': 'Please wait, generating story...'}, broadcast=True) calcsubmit(data) # Run the first action through the generator emit('from_server', {'cmd': 'scrolldown', 'data': ''}, broadcast=True) else: diff --git a/static/application.js b/static/application.js index b696e85c..f8692b7e 100644 --- a/static/application.js +++ b/static/application.js @@ -944,7 +944,7 @@ function highlightEditingChunks() { // This gets run every time the text in a chunk is edited // or a chunk is deleted function chunkOnDOMMutate(mutations, observer) { - if(!gametext_bound) { + if(!gametext_bound || !allowedit) { return; } var nodes = []; @@ -971,7 +971,7 @@ function chunkOnPaste(event) { // This gets run every time the caret moves in the editor function chunkOnSelectionChange(event) { - if(!gametext_bound || override_focusout) { + if(!gametext_bound || !allowedit || override_focusout) { override_focusout = false; return; } @@ -992,7 +992,7 @@ function chunkOnSelectionChange(event) { // This gets run when you defocus the editor by clicking // outside of the editor or by pressing escape or tab function chunkOnFocusOut(event) { - if(!gametext_bound || event.target !== game_text[0]) { + if(!gametext_bound || !allowedit || event.target !== game_text[0]) { return; } setTimeout(function() { @@ -1108,7 +1108,7 @@ $(document).ready(function(){ $('body').on('input', autofocus); $('#allowediting').prop('checked', allowedit).prop('disabled', false).change().off('change').on('change', function () { if(allowtoggle) { - allowedit = $(this).prop('checked'); + allowedit = gamestarted && $(this).prop('checked'); game_text.attr('contenteditable', allowedit); } }); From 97e1760af5d38ae191d0b53810424983d24eb5ed Mon Sep 17 00:00:00 2001 From: Gnome Ann <> Date: Tue, 28 Sep 2021 01:07:11 -0400 Subject: [PATCH 20/33] Prevent retry from popping chunks after edit/delete --- aiserver.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/aiserver.py b/aiserver.py index d37eb810..7ee18541 100644 --- a/aiserver.py +++ b/aiserver.py @@ -937,6 +937,7 @@ def actionsubmit(data, actionmode=0): set_aibusy(1) vars.recentback = False + vars.recentedit = False vars.actionmode = actionmode # "Action" mode @@ -993,13 +994,14 @@ def actionretry(data): # Remove last action if possible and resubmit if(vars.gamestarted if vars.useprompt else len(vars.actions) > 0): set_aibusy(1) - if(not vars.recentback and len(vars.actions) != 0 and len(vars.genseqs) == 0): # Don't pop if we're in the "Select sequence to keep" menu or if there are no non-prompt actions + if(not vars.recentback and not vars.recentedit and len(vars.actions) != 0 and len(vars.genseqs) == 0): # Don't pop if we're in the "Select sequence to keep" menu or if there are no non-prompt actions last_key = vars.actions.get_last_key() vars.actions.pop() remove_story_chunk(last_key + 1) vars.genseqs = [] calcsubmit('') vars.recentback = False + vars.recentedit = False elif(not vars.useprompt): emit('from_server', {'cmd': 'errmsg', 'data': "Please enable \"Always Add Prompt\" to retry with your prompt."}) @@ -1548,6 +1550,7 @@ def editrequest(n): # #==================================================================# def editsubmit(data): + vars.recentedit = True if(vars.editln == 0): vars.prompt = data else: @@ -1576,6 +1579,7 @@ def deleterequest(): # #==================================================================# def inlineedit(chunk, data): + vars.recentedit = True chunk = int(chunk) if(chunk == 0): if(len(data.strip()) == 0): @@ -1592,6 +1596,7 @@ def inlineedit(chunk, data): # #==================================================================# def inlinedelete(chunk): + vars.recentedit = True chunk = int(chunk) # Don't delete prompt if(chunk == 0): From 03c1a3ebf963c76db320aa1fafe67ca6282f8c1e Mon Sep 17 00:00:00 2001 From: Gnome Ann <> Date: Tue, 28 Sep 2021 01:10:20 -0400 Subject: [PATCH 21/33] Put vars.recentedit = True in deleterequest() for consistency --- aiserver.py | 1 + 1 file changed, 1 insertion(+) diff --git a/aiserver.py b/aiserver.py index 7ee18541..765727f4 100644 --- a/aiserver.py +++ b/aiserver.py @@ -1565,6 +1565,7 @@ def editsubmit(data): # #==================================================================# def deleterequest(): + vars.recentedit = True # Don't delete prompt if(vars.editln == 0): # Send error message From 8447ec44ab9d0b6ff6e24c5f529a0f2d979c9294 Mon Sep 17 00:00:00 2001 From: Gnome Ann <> Date: Tue, 28 Sep 2021 19:00:23 -0400 Subject: [PATCH 22/33] Fix world info screen and bump application.js version --- static/application.js | 2 ++ static/custom.css | 5 +++++ templates/index.html | 4 ++-- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/static/application.js b/static/application.js index f8692b7e..c7290052 100644 --- a/static/application.js +++ b/static/application.js @@ -466,6 +466,7 @@ function enterWiMode() { hide([button_actback, button_actmem, button_actretry, game_text]); show([wi_menu]); disableSendBtn(); + $("#gamescreen").addClass("wigamescreen"); } function exitWiMode() { @@ -474,6 +475,7 @@ function exitWiMode() { hide([wi_menu]); show([button_actback, button_actmem, button_actretry, game_text]); enableSendBtn(); + $("#gamescreen").removeClass("wigamescreen"); scrollToBottom(); } diff --git a/static/custom.css b/static/custom.css index fa9be0b4..2d12c00c 100644 --- a/static/custom.css +++ b/static/custom.css @@ -77,6 +77,11 @@ chunk.editing, chunk.editing * { font-family: "Helvetica"; } +#gamescreen.wigamescreen { + padding: 10px; + overflow-y: scroll; +} + #gamescreen span { align-self: flex-end; } diff --git a/templates/index.html b/templates/index.html index fbb90294..01ec91cf 100644 --- a/templates/index.html +++ b/templates/index.html @@ -6,14 +6,14 @@ - + - + From 4961273a26447e7d7c4d236d0429bf385830bf6d Mon Sep 17 00:00:00 2001 From: Gnome Ann <> Date: Tue, 28 Sep 2021 19:03:43 -0400 Subject: [PATCH 23/33] Don't scroll to the bottom of screen when exiting world info One of my earlier commits apparently removed the need for this. --- static/application.js | 1 - templates/index.html | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/static/application.js b/static/application.js index c7290052..04ca8cb9 100644 --- a/static/application.js +++ b/static/application.js @@ -476,7 +476,6 @@ function exitWiMode() { show([button_actback, button_actmem, button_actretry, game_text]); enableSendBtn(); $("#gamescreen").removeClass("wigamescreen"); - scrollToBottom(); } function returnWiList(ar) { diff --git a/templates/index.html b/templates/index.html index 01ec91cf..e8bfdb7a 100644 --- a/templates/index.html +++ b/templates/index.html @@ -6,7 +6,7 @@ - + From 9ab1d182ac8a5299a143f3327a302d43792bc070 Mon Sep 17 00:00:00 2001 From: Gnome Ann <> Date: Tue, 28 Sep 2021 19:48:43 -0400 Subject: [PATCH 24/33] Guard against empty prompts --- aiserver.py | 28 ++++++++++++++++++++++++---- static/application.js | 8 ++++++-- templates/index.html | 2 +- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/aiserver.py b/aiserver.py index 765727f4..f8f5648a 100644 --- a/aiserver.py +++ b/aiserver.py @@ -951,6 +951,9 @@ def actionsubmit(data, actionmode=0): vars.lastact = data if(not vars.gamestarted): + if(len(data.strip()) == 0): + set_aibusy(0) + return # Start the game vars.gamestarted = True # Save this first action as the prompt @@ -971,7 +974,10 @@ def actionsubmit(data, actionmode=0): if(vars.actionmode == 0): data = applyinputformatting(data) # Store the result in the Action log - vars.actions.append(data) + if(len(vars.prompt.strip()) == 0): + vars.prompt = data + else: + vars.actions.append(data) update_story_chunk('last') if(not vars.noai): @@ -1284,7 +1290,10 @@ def genresult(genout): genout = applyoutputformatting(genout) # Add formatted text to Actions array and refresh the game screen - vars.actions.append(genout) + if(len(vars.prompt.strip()) == 0): + vars.prompt = genout + else: + vars.actions.append(genout) update_story_chunk('last') emit('from_server', {'cmd': 'texteffect', 'data': vars.actions.get_last_key() if len(vars.actions) else 0}, broadcast=True) @@ -2102,8 +2111,19 @@ def loadRequest(loadpath): del vars.actions vars.actions = structures.KoboldStoryRegister() - for s in js["actions"]: - vars.actions.append(s) + actions = collections.deque(js["actions"]) + + if(len(vars.prompt.strip()) == 0): + while(len(actions)): + action = actions.popleft() + if(len(action.strip()) != 0): + vars.prompt = action + break + else: + vars.gamestarted = False + if(vars.gamestarted): + for s in actions: + vars.actions.append(s) # Try not to break older save files if("authorsnote" in js): diff --git a/static/application.js b/static/application.js index 04ca8cb9..17076459 100644 --- a/static/application.js +++ b/static/application.js @@ -494,7 +494,11 @@ function returnWiList(ar) { } function dosubmit() { - var txt = input_text.val(); + var txt = input_text.val().replace(/\u00a0/g, " "); + console.log(gamestarted) + if(!gamestarted && ((!adventure || !action_mode) && txt.trim().length == 0)) { + return; + } socket.send({'cmd': 'submit', 'actionmode': adventure ? action_mode : 0, 'data': txt}); if(memorymode) { memorytext = input_text.val(); @@ -882,7 +886,7 @@ function syncAllModifiedChunks(including_selected_chunks=false) { } function restorePrompt() { - if(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, " "); unbindGametext(); game_text[0].innerText = ""; diff --git a/templates/index.html b/templates/index.html index e8bfdb7a..95f3c31f 100644 --- a/templates/index.html +++ b/templates/index.html @@ -6,7 +6,7 @@ - + From af93c96c0f5c57b4f877a7256b4b73873d432013 Mon Sep 17 00:00:00 2001 From: Gnome Ann <> Date: Tue, 28 Sep 2021 19:50:00 -0400 Subject: [PATCH 25/33] Submit Action mode action in Story mode if action is empty --- aiserver.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/aiserver.py b/aiserver.py index f8f5648a..f8a3269b 100644 --- a/aiserver.py +++ b/aiserver.py @@ -944,7 +944,8 @@ def actionsubmit(data, actionmode=0): if(actionmode == 1): data = data.strip().lstrip('>') data = re.sub(r'\n+', ' ', data) - data = f"\n\n> {data}\n" + if(len(data)): + data = f"\n\n> {data}\n" # If we're not continuing, store a copy of the raw input if(data != ""): From 2b89bcb16efed0c21ef49c33dd4fca102a72f4eb Mon Sep 17 00:00:00 2001 From: Gnome Ann <> Date: Tue, 28 Sep 2021 21:04:26 -0400 Subject: [PATCH 26/33] Fix random story generator --- aiserver.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/aiserver.py b/aiserver.py index f8a3269b..a11d04ea 100644 --- a/aiserver.py +++ b/aiserver.py @@ -930,7 +930,7 @@ def settingschanged(): #==================================================================# # Take input text from SocketIO and decide what to do with it #==================================================================# -def actionsubmit(data, actionmode=0): +def actionsubmit(data, actionmode=0, force_submit=False): # Ignore new submissions if the AI is currently busy if(vars.aibusy): return @@ -952,7 +952,7 @@ def actionsubmit(data, actionmode=0): vars.lastact = data if(not vars.gamestarted): - if(len(data.strip()) == 0): + if(not force_submit and len(data.strip()) == 0): set_aibusy(0) return # Start the game @@ -2398,7 +2398,7 @@ def newGameRequest(): def randomGameRequest(topic): newGameRequest() vars.memory = "You generate the following " + topic + " story concept :" - actionsubmit("") + actionsubmit("", force_submit=True) vars.memory = "" #==================================================================# From bb323152d73d17d2ef48f46b11ecf7d1484d90e2 Mon Sep 17 00:00:00 2001 From: Gnome Ann <> Date: Tue, 28 Sep 2021 21:24:08 -0400 Subject: [PATCH 27/33] Disable vars.recentedit again --- aiserver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aiserver.py b/aiserver.py index a11d04ea..3175058b 100644 --- a/aiserver.py +++ b/aiserver.py @@ -1001,7 +1001,7 @@ def actionretry(data): # Remove last action if possible and resubmit if(vars.gamestarted if vars.useprompt else len(vars.actions) > 0): set_aibusy(1) - if(not vars.recentback and not vars.recentedit and len(vars.actions) != 0 and len(vars.genseqs) == 0): # Don't pop if we're in the "Select sequence to keep" menu or if there are no non-prompt actions + if(not vars.recentback and len(vars.actions) != 0 and len(vars.genseqs) == 0): # Don't pop if we're in the "Select sequence to keep" menu or if there are no non-prompt actions last_key = vars.actions.get_last_key() vars.actions.pop() remove_story_chunk(last_key + 1) From e6cd28243e77eb0aeab7b5d077d146ce5d04e0cc Mon Sep 17 00:00:00 2001 From: Gnome Ann <> Date: Tue, 28 Sep 2021 21:34:36 -0400 Subject: [PATCH 28/33] Scroll to the bottom of the gamescreen after retrying --- aiserver.py | 1 + 1 file changed, 1 insertion(+) diff --git a/aiserver.py b/aiserver.py index 3175058b..603ef0e7 100644 --- a/aiserver.py +++ b/aiserver.py @@ -1007,6 +1007,7 @@ def actionretry(data): remove_story_chunk(last_key + 1) vars.genseqs = [] calcsubmit('') + emit('from_server', {'cmd': 'scrolldown', 'data': ''}, broadcast=True) vars.recentback = False vars.recentedit = False elif(not vars.useprompt): From a179bb2820f2d76608482e6daf44f4c3828eb931 Mon Sep 17 00:00:00 2001 From: Gnome Ann <> Date: Tue, 28 Sep 2021 21:50:33 -0400 Subject: [PATCH 29/33] Bump version number to 1.16.2 --- aiserver.py | 2 +- templates/index.html | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/aiserver.py b/aiserver.py index 603ef0e7..567be4d0 100644 --- a/aiserver.py +++ b/aiserver.py @@ -1,6 +1,6 @@ #==================================================================# # KoboldAI -# Version: 1.16.1 +# Version: 1.16.2 # By: KoboldAIDev and the KoboldAI Community #==================================================================# diff --git a/templates/index.html b/templates/index.html index 95f3c31f..8e3558b9 100644 --- a/templates/index.html +++ b/templates/index.html @@ -6,14 +6,14 @@ - + - + From e32519774887613595f55d93570ffc7a9598a9b4 Mon Sep 17 00:00:00 2001 From: Gnome Ann <> Date: Thu, 30 Sep 2021 10:57:29 -0400 Subject: [PATCH 30/33] Fix for submitting memory/AN when memory is empty --- static/application.js | 3 +-- templates/index.html | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/static/application.js b/static/application.js index 17076459..51767f10 100644 --- a/static/application.js +++ b/static/application.js @@ -495,8 +495,7 @@ function returnWiList(ar) { function dosubmit() { var txt = input_text.val().replace(/\u00a0/g, " "); - console.log(gamestarted) - if(!gamestarted && ((!adventure || !action_mode) && txt.trim().length == 0)) { + if(!memorymode && !gamestarted && ((!adventure || !action_mode) && txt.trim().length == 0)) { return; } socket.send({'cmd': 'submit', 'actionmode': adventure ? action_mode : 0, 'data': txt}); diff --git a/templates/index.html b/templates/index.html index 8e3558b9..7098a824 100644 --- a/templates/index.html +++ b/templates/index.html @@ -6,7 +6,7 @@ - + From a923b3bfa7c569fc338e05a0db007116513a5be7 Mon Sep 17 00:00:00 2001 From: Gnome Ann <> Date: Thu, 30 Sep 2021 14:13:54 -0400 Subject: [PATCH 31/33] Safari compatibility Only tested on macOS so far, not iOS. --- static/application.js | 42 ++++++++++++++++++++++++++++++++++++++---- templates/index.html | 2 +- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/static/application.js b/static/application.js index 51767f10..83b4376b 100644 --- a/static/application.js +++ b/static/application.js @@ -78,6 +78,9 @@ var override_focusout = false; var sman_allow_delete = false; var sman_allow_rename = false; +// This is true iff [we're in macOS and the browser is Safari] or [we're in iOS] +var using_webkit_patch = false; + // Key states var shift_down = false; var do_clear_ent = false; @@ -985,10 +988,12 @@ function chunkOnSelectionChange(event) { highlightEditingChunks(); // Attempt to prevent Chromium-based browsers on Android from // scrolling away from the current selection - setTimeout(function() { - game_text.blur(); - game_text.focus(); - }, 144); + if(!using_webkit_patch) { + setTimeout(function() { + game_text.blur(); + game_text.focus(); + }, 144); + } }, 2); }, 2); } @@ -1091,6 +1096,35 @@ $(document).ready(function(){ rs_close = $("#btn_rsclose"); seqselmenu = $("#seqselmenu"); seqselcontents = $("#seqselcontents"); + + // A simple feature detection test to determine whether the user interface + // is using WebKit (Safari browser's rendering engine) because WebKit + // requires special treatment to work correctly with the KoboldAI editor + using_webkit_patch = (function() { + try { + var active_element = document.activeElement; + var c = document.createElement("chunk"); + var t = document.createTextNode("KoboldAI"); + c.appendChild(t); + game_text[0].appendChild(c); + var r = rangy.createRange(); + r.setStart(t, 6); + r.collapse(true); + var s = rangy.getSelection(); + s.removeAllRanges(); + s.addRange(r); + game_text.blur(); + game_text.focus(); + var offset = rangy.getSelection().focusOffset; + c.removeChild(t); + game_text[0].removeChild(c); + document.activeElement.blur(); + active_element.focus(); + return offset !== 6; + } catch (e) { + return false; + } + })(); // Connect to SocketIO server socket = io.connect(window.document.origin); diff --git a/templates/index.html b/templates/index.html index 7098a824..3a078f88 100644 --- a/templates/index.html +++ b/templates/index.html @@ -6,7 +6,7 @@ - + From 00c1f49ca14d44efafa7c14d1f46c422bd088fe1 Mon Sep 17 00:00:00 2001 From: Gnome Ann <> Date: Thu, 30 Sep 2021 17:52:35 -0400 Subject: [PATCH 32/33] Fix slow typing speed on mobile devices --- static/application.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/static/application.js b/static/application.js index 83b4376b..e6c05c08 100644 --- a/static/application.js +++ b/static/application.js @@ -977,7 +977,7 @@ function chunkOnPaste(event) { } // This gets run every time the caret moves in the editor -function chunkOnSelectionChange(event) { +function _chunkOnSelectionChange(event, do_blur_focus) { if(!gametext_bound || !allowedit || override_focusout) { override_focusout = false; return; @@ -988,7 +988,7 @@ function chunkOnSelectionChange(event) { highlightEditingChunks(); // Attempt to prevent Chromium-based browsers on Android from // scrolling away from the current selection - if(!using_webkit_patch) { + if(do_blur_focus && !using_webkit_patch) { setTimeout(function() { game_text.blur(); game_text.focus(); @@ -998,6 +998,14 @@ function chunkOnSelectionChange(event) { }, 2); } +function chunkOnSelectionChange(event) { + return _chunkOnSelectionChange(event, true); +} + +function chunkOnKeyDownSelectionChange(event) { + return _chunkOnSelectionChange(event, false); +} + // This gets run when you defocus the editor by clicking // outside of the editor or by pressing escape or tab function chunkOnFocusOut(event) { @@ -1432,7 +1440,7 @@ $(document).ready(function(){ ).on('click', chunkOnSelectionChange ).on('keydown', - chunkOnSelectionChange + chunkOnKeyDownSelectionChange ).on('focusout', chunkOnFocusOut ); From d7893a0b52541b2ec988dda1c7196a7645425586 Mon Sep 17 00:00:00 2001 From: Gnome Ann <> Date: Thu, 30 Sep 2021 19:00:15 -0400 Subject: [PATCH 33/33] Check for WebKit after connecting to the server For some reason the original way only works in Safari after pressing the refresh button. It did not work if you typed the URL into the address bar in Safari without refreshing afterwards. --- static/application.js | 61 ++++++++++++++++++++++--------------------- templates/index.html | 2 +- 2 files changed, 32 insertions(+), 31 deletions(-) diff --git a/static/application.js b/static/application.js index e6c05c08..04cf862e 100644 --- a/static/application.js +++ b/static/application.js @@ -79,7 +79,7 @@ var sman_allow_delete = false; var sman_allow_rename = false; // This is true iff [we're in macOS and the browser is Safari] or [we're in iOS] -var using_webkit_patch = false; +var using_webkit_patch = null; // Key states var shift_down = false; @@ -1105,35 +1105,6 @@ $(document).ready(function(){ seqselmenu = $("#seqselmenu"); seqselcontents = $("#seqselcontents"); - // A simple feature detection test to determine whether the user interface - // is using WebKit (Safari browser's rendering engine) because WebKit - // requires special treatment to work correctly with the KoboldAI editor - using_webkit_patch = (function() { - try { - var active_element = document.activeElement; - var c = document.createElement("chunk"); - var t = document.createTextNode("KoboldAI"); - c.appendChild(t); - game_text[0].appendChild(c); - var r = rangy.createRange(); - r.setStart(t, 6); - r.collapse(true); - var s = rangy.getSelection(); - s.removeAllRanges(); - s.addRange(r); - game_text.blur(); - game_text.focus(); - var offset = rangy.getSelection().focusOffset; - c.removeChild(t); - game_text[0].removeChild(c); - document.activeElement.blur(); - active_element.focus(); - return offset !== 6; - } catch (e) { - return false; - } - })(); - // Connect to SocketIO server socket = io.connect(window.document.origin); @@ -1158,6 +1129,36 @@ $(document).ready(function(){ game_text.attr('contenteditable', allowedit); } }); + // A simple feature detection test to determine whether the user interface + // is using WebKit (Safari browser's rendering engine) because WebKit + // requires special treatment to work correctly with the KoboldAI editor + if(using_webkit_patch === null) { + using_webkit_patch = (function() { + try { + var active_element = document.activeElement; + var c = document.createElement("chunk"); + var t = document.createTextNode("KoboldAI"); + c.appendChild(t); + game_text[0].appendChild(c); + var r = rangy.createRange(); + r.setStart(t, 6); + r.collapse(true); + var s = rangy.getSelection(); + s.removeAllRanges(); + s.addRange(r); + game_text.blur(); + game_text.focus(); + var offset = rangy.getSelection().focusOffset; + c.removeChild(t); + game_text[0].removeChild(c); + document.activeElement.blur(); + active_element.focus(); + return offset !== 6; + } catch (e) { + return false; + } + })(); + } } else if(msg.cmd == "updatescreen") { var _gamestarted = gamestarted; gamestarted = msg.gamestarted; diff --git a/templates/index.html b/templates/index.html index 3a078f88..55978aec 100644 --- a/templates/index.html +++ b/templates/index.html @@ -6,7 +6,7 @@ - +