mirror of
https://codeberg.org/emersion/gamja.git
synced 2024-11-28 10:16:40 -05:00
Add support for soju.im/filehost
For now, only handle paste events containing files. Co-authored-by: Alex McGrath <amk@amk.ie>
This commit is contained in:
parent
97b9efcc9f
commit
87e88cccca
3 changed files with 93 additions and 0 deletions
|
@ -1967,6 +1967,11 @@ export default class App extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let activeClient = null;
|
||||||
|
if (activeBuffer) {
|
||||||
|
activeClient = this.clients.get(activeBuffer.server);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.state.connectForm) {
|
if (this.state.connectForm) {
|
||||||
let status = activeServer ? activeServer.status : ServerStatus.DISCONNECTED;
|
let status = activeServer ? activeServer.status : ServerStatus.DISCONNECTED;
|
||||||
let connecting = status === ServerStatus.CONNECTING || status === ServerStatus.REGISTERING;
|
let connecting = status === ServerStatus.CONNECTING || status === ServerStatus.REGISTERING;
|
||||||
|
@ -2208,6 +2213,7 @@ export default class App extends Component {
|
||||||
${memberList}
|
${memberList}
|
||||||
<${Composer}
|
<${Composer}
|
||||||
ref=${this.composer}
|
ref=${this.composer}
|
||||||
|
client=${activeClient}
|
||||||
readOnly=${composerReadOnly}
|
readOnly=${composerReadOnly}
|
||||||
onSubmit=${this.handleComposerSubmit}
|
onSubmit=${this.handleComposerSubmit}
|
||||||
autocomplete=${this.autocomplete}
|
autocomplete=${this.autocomplete}
|
||||||
|
|
|
@ -1,5 +1,16 @@
|
||||||
import { html, Component, createRef } from "../lib/index.js";
|
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 {
|
export default class Composer extends Component {
|
||||||
state = {
|
state = {
|
||||||
text: "",
|
text: "",
|
||||||
|
@ -13,6 +24,7 @@ export default class Composer extends Component {
|
||||||
this.handleInput = this.handleInput.bind(this);
|
this.handleInput = this.handleInput.bind(this);
|
||||||
this.handleSubmit = this.handleSubmit.bind(this);
|
this.handleSubmit = this.handleSubmit.bind(this);
|
||||||
this.handleInputKeyDown = this.handleInputKeyDown.bind(this);
|
this.handleInputKeyDown = this.handleInputKeyDown.bind(this);
|
||||||
|
this.handleInputPaste = this.handleInputPaste.bind(this);
|
||||||
this.handleWindowKeyDown = this.handleWindowKeyDown.bind(this);
|
this.handleWindowKeyDown = this.handleWindowKeyDown.bind(this);
|
||||||
this.handleWindowPaste = this.handleWindowPaste.bind(this);
|
this.handleWindowPaste = this.handleWindowPaste.bind(this);
|
||||||
}
|
}
|
||||||
|
@ -116,6 +128,71 @@ export default class Composer extends Component {
|
||||||
this.setState({ text: autocomplete.text });
|
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) {
|
handleWindowKeyDown(event) {
|
||||||
// If an <input> or <button> is focused, ignore.
|
// If an <input> or <button> is focused, ignore.
|
||||||
if (document.activeElement && document.activeElement !== document.body) {
|
if (document.activeElement && document.activeElement !== document.body) {
|
||||||
|
@ -173,6 +250,11 @@ export default class Composer extends Component {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event.clipboardData.files.length > 0) {
|
||||||
|
this.handleInputPaste(event);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let text = event.clipboardData.getData("text");
|
let text = event.clipboardData.getData("text");
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
@ -228,6 +310,7 @@ export default class Composer extends Component {
|
||||||
placeholder=${placeholder}
|
placeholder=${placeholder}
|
||||||
enterkeyhint="send"
|
enterkeyhint="send"
|
||||||
onKeyDown=${this.handleInputKeyDown}
|
onKeyDown=${this.handleInputKeyDown}
|
||||||
|
onPaste=${this.handleInputPaste}
|
||||||
maxlength=${this.props.maxLen}
|
maxlength=${this.props.maxLen}
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -526,6 +526,10 @@ export class Isupport {
|
||||||
}
|
}
|
||||||
return parseInt(this.raw.get("LINELEN"), 10);
|
return parseInt(this.raw.get("LINELEN"), 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filehost() {
|
||||||
|
return this.raw.get("SOJU.IM/FILEHOST");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMaxPrivmsgLen(isupport, nick, target) {
|
export function getMaxPrivmsgLen(isupport, nick, target) {
|
||||||
|
|
Loading…
Reference in a new issue