mirror of
https://github.com/thelounge/thelounge.git
synced 2024-05-06 08:23:19 +02:00
Implement history navigation WIP.
This commit is contained in:
parent
76d736199b
commit
fcf3f7caac
|
@ -51,7 +51,9 @@
|
||||||
<WysiwygInput
|
<WysiwygInput
|
||||||
ref="wysiwyg"
|
ref="wysiwyg"
|
||||||
:placeholder="getInputPlaceholder(channel)"
|
:placeholder="getInputPlaceholder(channel)"
|
||||||
|
@change="onChange"
|
||||||
@submit="onSubmit"
|
@submit="onSubmit"
|
||||||
|
@navigate="onNavigate"
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
id="format-tooltip"
|
id="format-tooltip"
|
||||||
|
@ -257,10 +259,10 @@ export default {
|
||||||
upload.abort();
|
upload.abort();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
setPendingMessage(e) {
|
setPendingMessage(text) {
|
||||||
this.channel.pendingMessage = e.target.value;
|
console.log("setPendingMessage", text);
|
||||||
|
this.channel.pendingMessage = text;
|
||||||
this.channel.inputHistoryPosition = 0;
|
this.channel.inputHistoryPosition = 0;
|
||||||
this.setInputSize();
|
|
||||||
},
|
},
|
||||||
getInputPlaceholder(channel) {
|
getInputPlaceholder(channel) {
|
||||||
if (channel.type === "channel" || channel.type === "query") {
|
if (channel.type === "channel" || channel.type === "query") {
|
||||||
|
@ -269,6 +271,11 @@ export default {
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
},
|
},
|
||||||
|
onChange() {
|
||||||
|
console.log("onChange");
|
||||||
|
this.setPendingMessage(this.$refs.wysiwyg.getHtmlContent());
|
||||||
|
console.log(this.channel.pendingMessage);
|
||||||
|
},
|
||||||
onSubmit(e) {
|
onSubmit(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
@ -277,15 +284,54 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
const target = this.channel.id;
|
const target = this.channel.id;
|
||||||
// const content = this.$refs.wysiwyg.getHtmlContent(); TODO: use this for input history
|
const text = this.$refs.wysiwyg.getHtmlContent(); // TODO: use this for input history
|
||||||
const lines = this.$refs.wysiwyg.getIrcLines();
|
const lines = this.$refs.wysiwyg.getIrcLines();
|
||||||
|
const message = lines.join("\n");
|
||||||
|
|
||||||
|
if (!message) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.channel.inputHistoryPosition = 0;
|
||||||
|
this.channel.pendingMessage = "";
|
||||||
|
// this.$refs.input.value = ""; TODO: check if necessary
|
||||||
|
//this.setInputSize();
|
||||||
|
|
||||||
|
// Store new message in history if last message isn't already equal
|
||||||
|
if (this.channel.inputHistory[1] !== text) {
|
||||||
|
this.channel.inputHistory.splice(1, 0, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit input history to a 100 entries
|
||||||
|
if (this.channel.inputHistory.length > 100) {
|
||||||
|
this.channel.inputHistory.pop();
|
||||||
|
}
|
||||||
|
|
||||||
this.$refs.wysiwyg.clear();
|
this.$refs.wysiwyg.clear();
|
||||||
this.$refs.wysiwyg.focus();
|
this.$refs.wysiwyg.focus();
|
||||||
|
|
||||||
const message = lines.join("\n");
|
console.log(`SENDING: "${message}"`);
|
||||||
socket.emit("input", {target, text: 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() {
|
onSubmit() {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div id="input" ref="container" class="wysiwyg-container">
|
<div id="input" ref="container" class="wysiwyg-container">
|
||||||
<div ref="indicator" class="indicator"></div>
|
<div ref="selectionIndicator" class="selectionIndicator"></div>
|
||||||
|
<div ref="textboxIndicator" class="textboxIndicator"></div>
|
||||||
<IrcColorPicker ref="colorpicker" />
|
<IrcColorPicker ref="colorpicker" />
|
||||||
<div
|
<div
|
||||||
id="wysiwyg-input"
|
id="wysiwyg-input"
|
||||||
|
@ -12,6 +13,8 @@
|
||||||
:aria-label="placeholder"
|
:aria-label="placeholder"
|
||||||
@focus="onFocus"
|
@focus="onFocus"
|
||||||
@blur="onBlur"
|
@blur="onBlur"
|
||||||
|
@keydown.exact.up="navigate('up')"
|
||||||
|
@keydown.exact.down="navigate('down')"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -44,7 +47,10 @@
|
||||||
display: block;
|
display: block;
|
||||||
content: attr(data-placeholder);
|
content: attr(data-placeholder);
|
||||||
}
|
}
|
||||||
|
.wysiwyg-container .wysiwyg-input::after {
|
||||||
|
content: "";
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
.wysiwyg-container .wysiwyg-input sub {
|
.wysiwyg-container .wysiwyg-input sub {
|
||||||
font-size: inherit;
|
font-size: inherit;
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
|
@ -52,12 +58,18 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO: remove this */
|
/* TODO: remove this */
|
||||||
.wysiwyg-container .indicator {
|
.wysiwyg-container .selectionIndicator,
|
||||||
|
.wysiwyg-container .textboxIndicator {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
display: none;
|
|
||||||
border: 1px solid black;
|
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.wysiwyg-container .textboxIndicator {
|
||||||
|
border: 1px solid cyan;
|
||||||
|
}
|
||||||
|
.wysiwyg-container .textboxIndicator {
|
||||||
|
border: 1px solid magenta;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
@ -70,6 +82,7 @@ import {
|
||||||
getLinesAsFragments,
|
getLinesAsFragments,
|
||||||
cloneNodeTreeSelective,
|
cloneNodeTreeSelective,
|
||||||
cleanWysiwygMarkup,
|
cleanWysiwygMarkup,
|
||||||
|
splitDomAtElementBoundaries,
|
||||||
} from "../js/helpers/wysiwyg";
|
} from "../js/helpers/wysiwyg";
|
||||||
|
|
||||||
// Mapping of HTML tag names to IRC format control characters
|
// Mapping of HTML tag names to IRC format control characters
|
||||||
|
@ -185,7 +198,7 @@ export default {
|
||||||
});
|
});
|
||||||
|
|
||||||
this.$refs.input.addEventListener("keyup", () => {
|
this.$refs.input.addEventListener("keyup", () => {
|
||||||
this.updateSelectionIndicator();
|
this.updateIndicators();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Formatting
|
// Formatting
|
||||||
|
@ -208,10 +221,11 @@ export default {
|
||||||
return !this.$refs.input.textContent;
|
return !this.$refs.input.textContent;
|
||||||
},
|
},
|
||||||
getHtmlContent() {
|
getHtmlContent() {
|
||||||
// TODO: format & setter
|
this.cleanInputDom();
|
||||||
// TODO Move <br> removal to cleanup and do it dom based instead of string based
|
|
||||||
let html = this.$refs.input.innerHTML;
|
let html = this.$refs.input.innerHTML;
|
||||||
|
|
||||||
|
// TODO Move <br> removal to cleanup and do it dom based instead of string based
|
||||||
if (html.endsWith("<br>")) {
|
if (html.endsWith("<br>")) {
|
||||||
// Remove the last trailing newline
|
// Remove the last trailing newline
|
||||||
html = html.substring(0, html.length - 4);
|
html = html.substring(0, html.length - 4);
|
||||||
|
@ -276,8 +290,6 @@ export default {
|
||||||
|
|
||||||
const html = this.$refs.input.innerHTML;
|
const html = this.$refs.input.innerHTML;
|
||||||
const ircFormat = toIrcFormat(html);
|
const ircFormat = toIrcFormat(html);
|
||||||
|
|
||||||
document.getElementById("input").value = ircFormat;
|
|
||||||
},
|
},
|
||||||
onInput() {
|
onInput() {
|
||||||
this.onChange();
|
this.onChange();
|
||||||
|
@ -287,7 +299,9 @@ export default {
|
||||||
this.setAutoHeight();
|
this.setAutoHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateSelectionIndicator();
|
this.updateIndicators();
|
||||||
|
|
||||||
|
this.$emit("change");
|
||||||
},
|
},
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
|
@ -341,7 +355,6 @@ export default {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
pickColor() {
|
pickColor() {
|
||||||
// TODO: This should be broken up into parts
|
|
||||||
const sel = window.getSelection();
|
const sel = window.getSelection();
|
||||||
|
|
||||||
// If there is no selection do nothing (sel.type is `Caret`)
|
// If there is no selection do nothing (sel.type is `Caret`)
|
||||||
|
@ -349,13 +362,14 @@ export default {
|
||||||
return;
|
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 range = sel.getRangeAt(0);
|
||||||
const rect = range.getBoundingClientRect();
|
const rect = range.getBoundingClientRect();
|
||||||
const pos = {x: rect.left, y: rect.top - 5};
|
const pos = {x: rect.left, y: rect.top - 5};
|
||||||
|
|
||||||
// Open the color picker above the current selection
|
// Open the color picker above the current selection
|
||||||
this.$refs.colorpicker.open(pos, (colors) => {
|
this.$refs.colorpicker.open(pos, (colors) => {
|
||||||
|
// Focus the input after the color selector has completed
|
||||||
this.focus();
|
this.focus();
|
||||||
|
|
||||||
// If the color picker was exited or no colors were chosen do nothing
|
// If the color picker was exited or no colors were chosen do nothing
|
||||||
|
@ -379,6 +393,8 @@ export default {
|
||||||
const currentSelection = range.extractContents();
|
const currentSelection = range.extractContents();
|
||||||
|
|
||||||
// Clone the selected tree, remove any spans but keep their content
|
// 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(
|
const newTree = cloneNodeTreeSelective(
|
||||||
currentSelection,
|
currentSelection,
|
||||||
(el) => el.nodeName === "SPAN"
|
(el) => el.nodeName === "SPAN"
|
||||||
|
@ -392,18 +408,9 @@ export default {
|
||||||
// Insert the color span into the container
|
// Insert the color span into the container
|
||||||
range.insertNode(span);
|
range.insertNode(span);
|
||||||
|
|
||||||
// Split the dom at the boundaries of the selection to break any possible parent spans
|
// Split the dom preserving styles at the start and end of the range
|
||||||
// This is done by removing and re-inserting the nodes before and after the selection
|
// This prevents nesting between the selection boundary (span)
|
||||||
|
splitDomAtElementBoundaries(this.$refs.input, span, range);
|
||||||
// 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());
|
|
||||||
|
|
||||||
if (span.parentNode.nodeName === "SPAN") {
|
if (span.parentNode.nodeName === "SPAN") {
|
||||||
// If still nested in a color tag, replace the parent with the current color
|
// If still nested in a color tag, replace the parent with the current color
|
||||||
|
@ -415,21 +422,67 @@ export default {
|
||||||
range.setEndBefore(span);
|
range.setEndBefore(span);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
updateSelectionIndicator() {
|
updateIndicators() {
|
||||||
// TODO: this is only for debugging
|
// TODO: this is only for debugging
|
||||||
const sel = window.getSelection();
|
const sel = window.getSelection();
|
||||||
const range = sel.getRangeAt(0);
|
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";
|
const inputRect = this.$refs.input.getBoundingClientRect();
|
||||||
indicator.style.left = rect.left + "px";
|
|
||||||
indicator.style.width = rect.width + "px";
|
this.$refs.textboxIndicator.style.top = inputRect.top + "px";
|
||||||
indicator.style.height = rect.height + "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;
|
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"});
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -68,6 +68,21 @@ export function getLinesAsFragments(element, range) {
|
||||||
return fragments;
|
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
|
// Recursively clone a node tree and omit elements that
|
||||||
// dont pass the test while keeping their children
|
// dont pass the test while keeping their children
|
||||||
export function cloneNodeTreeSelective(from, omitTest) {
|
export function cloneNodeTreeSelective(from, omitTest) {
|
||||||
|
|
Loading…
Reference in a new issue