diff --git a/client/components/ChatInput.vue b/client/components/ChatInput.vue
index 612ff16b..d0744dba 100644
--- a/client/components/ChatInput.vue
+++ b/client/components/ChatInput.vue
@@ -51,7 +51,9 @@
100) {
+ this.channel.inputHistory.pop();
+ }
this.$refs.wysiwyg.clear();
this.$refs.wysiwyg.focus();
- const message = lines.join("\n");
+ console.log(`SENDING: "${message}"`);
socket.emit("input", {target, text: message});
},
+ onNavigate(data) {
+ console.log("onNavigate", data);
+ const {channel} = this;
+
+ if (channel.inputHistoryPosition === 0) {
+ channel.inputHistory[channel.inputHistoryPosition] = channel.pendingMessage;
+ }
+
+ if (data.direction === "up") {
+ if (channel.inputHistoryPosition < channel.inputHistory.length - 1) {
+ channel.inputHistoryPosition++;
+ }
+ } else if (channel.inputHistoryPosition > 0) {
+ channel.inputHistoryPosition--;
+ }
+
+ channel.pendingMessage = channel.inputHistory[channel.inputHistoryPosition];
+ this.$refs.wysiwyg.setHtmlContent(channel.pendingMessage);
+ },
/*
onSubmit() {
diff --git a/client/components/WysiwygInput.vue b/client/components/WysiwygInput.vue
index bdd177ce..d63ec468 100644
--- a/client/components/WysiwygInput.vue
+++ b/client/components/WysiwygInput.vue
@@ -1,6 +1,7 @@
@@ -44,7 +47,10 @@
display: block;
content: attr(data-placeholder);
}
-
+.wysiwyg-container .wysiwyg-input::after {
+ content: "";
+ display: inline-block;
+}
.wysiwyg-container .wysiwyg-input sub {
font-size: inherit;
font-family: monospace;
@@ -52,12 +58,18 @@
}
/* TODO: remove this */
-.wysiwyg-container .indicator {
+.wysiwyg-container .selectionIndicator,
+.wysiwyg-container .textboxIndicator {
display: inline-block;
- display: none;
- border: 1px solid black;
position: fixed;
z-index: 100;
+ pointer-events: none;
+}
+.wysiwyg-container .textboxIndicator {
+ border: 1px solid cyan;
+}
+.wysiwyg-container .textboxIndicator {
+ border: 1px solid magenta;
}
@@ -70,6 +82,7 @@ import {
getLinesAsFragments,
cloneNodeTreeSelective,
cleanWysiwygMarkup,
+ splitDomAtElementBoundaries,
} from "../js/helpers/wysiwyg";
// Mapping of HTML tag names to IRC format control characters
@@ -185,7 +198,7 @@ export default {
});
this.$refs.input.addEventListener("keyup", () => {
- this.updateSelectionIndicator();
+ this.updateIndicators();
});
// Formatting
@@ -208,10 +221,11 @@ export default {
return !this.$refs.input.textContent;
},
getHtmlContent() {
- // TODO: format & setter
- // TODO Move
removal to cleanup and do it dom based instead of string based
+ this.cleanInputDom();
+
let html = this.$refs.input.innerHTML;
+ // TODO Move
removal to cleanup and do it dom based instead of string based
if (html.endsWith("
")) {
// Remove the last trailing newline
html = html.substring(0, html.length - 4);
@@ -276,8 +290,6 @@ export default {
const html = this.$refs.input.innerHTML;
const ircFormat = toIrcFormat(html);
-
- document.getElementById("input").value = ircFormat;
},
onInput() {
this.onChange();
@@ -287,7 +299,9 @@ export default {
this.setAutoHeight();
}
- this.updateSelectionIndicator();
+ this.updateIndicators();
+
+ this.$emit("change");
},
// Actions
@@ -341,7 +355,6 @@ export default {
});
},
pickColor() {
- // TODO: This should be broken up into parts
const sel = window.getSelection();
// If there is no selection do nothing (sel.type is `Caret`)
@@ -349,13 +362,14 @@ export default {
return;
}
- // Get the cursor positon
+ // Get the position for the color selector based on the cursor positon of the selection
const range = sel.getRangeAt(0);
const rect = range.getBoundingClientRect();
const pos = {x: rect.left, y: rect.top - 5};
// Open the color picker above the current selection
this.$refs.colorpicker.open(pos, (colors) => {
+ // Focus the input after the color selector has completed
this.focus();
// If the color picker was exited or no colors were chosen do nothing
@@ -379,6 +393,8 @@ export default {
const currentSelection = range.extractContents();
// Clone the selected tree, remove any spans but keep their content
+ // This ensures that the selection to be colored will not contain
+ // any nested coloring
const newTree = cloneNodeTreeSelective(
currentSelection,
(el) => el.nodeName === "SPAN"
@@ -392,18 +408,9 @@ export default {
// Insert the color span into the container
range.insertNode(span);
- // Split the dom at the boundaries of the selection to break any possible parent spans
- // This is done by removing and re-inserting the nodes before and after the selection
-
- // Extract and re-insert everything before the users selection
- range.setStart(this.$refs.input, 0);
- range.setEndBefore(span);
- range.insertNode(range.extractContents());
-
- // Extract and re-insert everything after the users selection
- range.selectNodeContents(this.$refs.input);
- range.setStartAfter(span);
- range.insertNode(range.extractContents());
+ // Split the dom preserving styles at the start and end of the range
+ // This prevents nesting between the selection boundary (span)
+ splitDomAtElementBoundaries(this.$refs.input, span, range);
if (span.parentNode.nodeName === "SPAN") {
// If still nested in a color tag, replace the parent with the current color
@@ -415,21 +422,67 @@ export default {
range.setEndBefore(span);
});
},
- updateSelectionIndicator() {
+ updateIndicators() {
// TODO: this is only for debugging
const sel = window.getSelection();
const range = sel.getRangeAt(0);
- const rect = range.getBoundingClientRect();
+ const selectionRect = range.getBoundingClientRect();
- const indicator = this.$refs.indicator;
+ this.$refs.selectionIndicator.style.top = selectionRect.top + "px";
+ this.$refs.selectionIndicator.style.left = selectionRect.left + "px";
+ this.$refs.selectionIndicator.style.width = selectionRect.width + "px";
+ this.$refs.selectionIndicator.style.height = selectionRect.height + "px";
- indicator.style.top = rect.top + "px";
- indicator.style.left = rect.left + "px";
- indicator.style.width = rect.width + "px";
- indicator.style.height = rect.height + "px";
+ const inputRect = this.$refs.input.getBoundingClientRect();
+
+ this.$refs.textboxIndicator.style.top = inputRect.top + "px";
+ this.$refs.textboxIndicator.style.left = inputRect.left + "px";
+ this.$refs.textboxIndicator.style.width = inputRect.width + "px";
+ this.$refs.textboxIndicator.style.height = inputRect.height + "px";
+
+ // const topDist = Math.abs(selectionRect.top - inputRect.top)
+ // const bottomDist = Math.abs(selectionRect.bottom - inputRect.bottom)
+ // console.log("===========================")
+ // // console.log("selectionRect", selectionRect);
+ // // console.log("inputRect", inputRect);
+ // console.log("topDist", topDist)
+ // console.log("bottomDist", bottomDist)
return false;
},
+
+ // Emit up and down events when on first or last line
+ navigate(direction) {
+ // TODO: this is only for debugging
+ const sel = window.getSelection();
+ const range = sel.getRangeAt(0);
+
+ const selectionRect = range.getBoundingClientRect();
+ const inputRect = this.$refs.input.getBoundingClientRect();
+
+ const topDist = Math.abs(selectionRect.top - inputRect.top);
+ const bottomDist = Math.abs(selectionRect.bottom - inputRect.bottom);
+
+ const threshhold = 3; // Max diff
+ if (
+ direction === "up" &&
+ (this.isEmpty() ||
+ topDist <= threshhold ||
+ sel.anchorNode === this.$refs.input ||
+ sel.anchorNode === this.$refs.input.firstChild)
+ ) {
+ this.$emit("navigate", {direction: "up"});
+ }
+ if (
+ direction === "down" &&
+ (this.isEmpty() ||
+ bottomDist <= threshhold ||
+ sel.anchorNode === this.$refs.input ||
+ sel.anchorNode === this.$refs.input.lastChild)
+ ) {
+ this.$emit("navigate", {direction: "down"});
+ }
+ },
},
};
diff --git a/client/js/helpers/wysiwyg.js b/client/js/helpers/wysiwyg.js
index 8efff45f..f25f8047 100644
--- a/client/js/helpers/wysiwyg.js
+++ b/client/js/helpers/wysiwyg.js
@@ -68,6 +68,21 @@ export function getLinesAsFragments(element, range) {
return fragments;
}
+export function splitDomAtElementBoundaries(root, element, range) {
+ // Split the dom at the boundaries of the selection to break any possible parent spans
+ // This is done by removing and re-inserting the nodes before and after the selection
+
+ // Extract and re-insert everything before the users selection
+ range.setStart(root, 0);
+ range.setEndBefore(element);
+ range.insertNode(range.extractContents());
+
+ // Extract and re-insert everything after the users selection
+ range.selectNodeContents(root);
+ range.setStartAfter(element);
+ range.insertNode(range.extractContents());
+}
+
// Recursively clone a node tree and omit elements that
// dont pass the test while keeping their children
export function cloneNodeTreeSelective(from, omitTest) {