import { html, Component, createRef } from "../lib/index.js";
function encodeContentDisposition(filename) {
// Encode filename according to RFC 5987 if necessary. Note,
// encodeURIComponent will percent-encode a superset of attr-char.
let encodedFilename = encodeURIComponent(filename);
if (encodedFilename === filename) {
return "attachment; filename=\"" + filename + "\"";
} else {
return "attachment; filename*=UTF-8''" + encodedFilename;
}
}
export default class Composer extends Component {
state = {
text: "",
};
textInput = createRef();
lastAutocomplete = null;
constructor(props) {
super(props);
this.handleInput = this.handleInput.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleInputKeyDown = this.handleInputKeyDown.bind(this);
this.handleInputPaste = this.handleInputPaste.bind(this);
this.handleWindowKeyDown = this.handleWindowKeyDown.bind(this);
this.handleWindowPaste = this.handleWindowPaste.bind(this);
}
handleInput(event) {
this.setState({ [event.target.name]: event.target.value });
if (this.props.readOnly && event.target.name === "text" && !event.target.value) {
event.target.blur();
}
}
handleSubmit(event) {
event.preventDefault();
this.props.onSubmit(this.state.text);
this.setState({ text: "" });
}
handleInputKeyDown(event) {
let input = event.target;
if (!this.props.autocomplete || event.key !== "Tab") {
return;
}
if (input.selectionStart !== input.selectionEnd) {
return;
}
event.preventDefault();
let carretPos = input.selectionStart;
let text = this.state.text;
let autocomplete;
if (this.lastAutocomplete && this.lastAutocomplete.text === text && this.lastAutocomplete.carretPos === carretPos) {
autocomplete = this.lastAutocomplete;
} else {
this.lastAutocomplete = null;
let wordStart;
for (wordStart = carretPos - 1; wordStart >= 0; wordStart--) {
if (text[wordStart] === " ") {
break;
}
}
wordStart++;
let wordEnd;
for (wordEnd = carretPos; wordEnd < text.length; wordEnd++) {
if (text[wordEnd] === " ") {
break;
}
}
let word = text.slice(wordStart, wordEnd);
if (!word) {
return;
}
let replacements = this.props.autocomplete(word);
if (replacements.length === 0) {
return;
}
autocomplete = {
text,
carretPos: input.selectionStart,
prefix: text.slice(0, wordStart),
suffix: text.slice(wordEnd),
replacements,
replIndex: -1,
};
}
let n = autocomplete.replacements.length;
if (event.shiftKey) {
autocomplete.replIndex--;
} else {
autocomplete.replIndex++;
}
autocomplete.replIndex = (autocomplete.replIndex + n) % n;
let repl = autocomplete.replacements[autocomplete.replIndex];
if (!autocomplete.prefix && !autocomplete.suffix) {
if (repl.startsWith("/")) {
repl += " ";
} else {
repl += ": ";
}
}
autocomplete.text = autocomplete.prefix + repl + autocomplete.suffix;
autocomplete.carretPos = autocomplete.prefix.length + repl.length;
input.value = autocomplete.text;
input.selectionStart = autocomplete.carretPos;
input.selectionEnd = input.selectionStart;
this.lastAutocomplete = autocomplete;
this.setState({ text: autocomplete.text });
}
async handleInputPaste(event) {
let client = this.props.client;
if (!event.clipboardData.files.length || !client || this.props.readOnly) {
return;
}
let endpoint = client.isupport.filehost();
if (!endpoint) {
return;
}
event.preventDefault();
event.stopImmediatePropagation();
// TODO: support more than one file
let file = event.clipboardData.files.item(0);
let auth;
if (client.params.saslPlain) {
let params = client.params.saslPlain;
auth = "Basic " + btoa(params.username + ":" + params.password);
} else if (client.params.saslOauthBearer) {
auth = "Bearer " + client.params.saslOauthBearer.token;
}
let headers = {
"Content-Length": file.size,
"Content-Disposition": encodeContentDisposition(file.name),
};
if (file.type) {
headers["Content-Type"] = file.type;
}
if (auth) {
headers["Authorization"] = auth;
}
// TODO: show a loading UI while uploading
// TODO: show a cancel button
let resp = await fetch(endpoint, {
method: "POST",
body: file,
headers,
credentials: "include",
});
if (!resp.ok) {
throw new Error(`HTTP request failed (${resp.status})`);
}
let loc = resp.headers.get("Location");
if (!loc) {
throw new Error("filehost response missing Location header field");
}
let uploadURL = new URL(loc, endpoint);
this.setState((state) => {
if (state.text) {
return { text: state.text + " " + uploadURL.toString() };
} else {
return { text: uploadURL.toString() };
}
});
}
handleWindowKeyDown(event) {
// If an or