diff --git a/src/assets/css/chat.scss b/src/assets/css/chat.scss
index c52fa26..4cfec2b 100644
--- a/src/assets/css/chat.scss
+++ b/src/assets/css/chat.scss
@@ -61,6 +61,10 @@ $chat-text-size: 0.7pt;
margin: 0;
padding-left: $chat-standard-padding-s;
padding-right: $chat-standard-padding-s;
+ .currentImage {
+ width: 6 * $chat-standard-padding-s;
+ height: 6 * $chat-standard-padding-s;
+ }
}
.input-message {
@@ -72,7 +76,7 @@ $chat-text-size: 0.7pt;
padding: 0 10px 0 10px;
margin: $chat-standard-padding-xs 0 0 0;
color: #999999;
- background-color: #ffffff;
+ //background-color: #ffffff;
overflow: hidden;
border: 1px solid #cccccc;
border-radius: 0;
@@ -88,6 +92,9 @@ $chat-text-size: 0.7pt;
textarea {
line-height: 1.1rem;
}
+ .v-input__prepend-outer {
+ margin-top: 0px;
+ }
}
}
diff --git a/src/components/Chat.vue b/src/components/Chat.vue
index a33619b..37531eb 100644
--- a/src/components/Chat.vue
+++ b/src/components/Chat.vue
@@ -51,8 +51,13 @@
{{ messageEventDisplayName(event) }}
-
- {{messageEventDisplayName(event).substring(0,1).toUpperCase()}}
+
+ {{
+ messageEventDisplayName(event).substring(0, 1).toUpperCase()
+ }}
@@ -78,12 +83,14 @@
- {{ stateEventDisplayName(event) }} changed room name to {{ event.getContent().name }}
+ {{ stateEventDisplayName(event) }} changed room name to
+ {{ event.getContent().name }}
- {{ stateEventDisplayName(event) }} changed topic to {{ event.getContent().topic }}
+ {{ stateEventDisplayName(event) }} changed topic to
+ {{ event.getContent().topic }}
@@ -91,15 +98,14 @@
{{ stateEventDisplayName(event) }} changed the room avatar
-
Event: {{ event.getType() }}
-
+
Event: {{ event.getType() }}
-
- Someone is typing...
-
@@ -128,6 +174,7 @@ import { TimelineWindow, EventTimeline } from "matrix-js-sdk";
function ScrollPosition(node) {
this.node = node;
this.previousScrollHeightMinusTop = 0;
+ this.previousScrollTop = 0;
this.readyFor = "up";
}
@@ -135,18 +182,19 @@ ScrollPosition.prototype.restore = function () {
if (this.readyFor === "up") {
this.node.scrollTop =
this.node.scrollHeight - this.previousScrollHeightMinusTop;
+ } else {
+ this.node.scrollTop = this.previousScrollTop;
}
-
- // 'down' doesn't need to be special cased unless the
- // content was flowing upwards, which would only happen
- // if the container is position: absolute, bottom: 0 for
- // a Facebook messages effect
};
ScrollPosition.prototype.prepareFor = function (direction) {
this.readyFor = direction || "up";
- this.previousScrollHeightMinusTop =
- this.node.scrollHeight - this.node.scrollTop;
+ if (this.readyFor === "up") {
+ this.previousScrollHeightMinusTop =
+ this.node.scrollHeight - this.node.scrollTop;
+ } else {
+ this.previousScrollTop = this.node.scrollTop;
+ }
};
export default {
@@ -159,6 +207,11 @@ export default {
contactIsTyping: false,
timelineWindow: null,
scrollPosition: null,
+ currentImageInput: null,
+ currentImageInputPath: null,
+ currentSendOperation: null,
+ currentSendProgress: null,
+ currentSendError: null,
}),
mounted() {
@@ -167,7 +220,6 @@ export default {
this.$matrix.on("Room.timeline", this.onEvent);
this.$matrix.on("RoomMember.typing", this.onUserTyping);
-
},
destroyed() {
@@ -193,7 +245,7 @@ export default {
// Clear old events
this.events = [];
- this.timelineWindow = null;
+ this.timelineWindow = null;
this.contactIsTyping = false;
if (!this.roomId) {
@@ -219,12 +271,12 @@ export default {
methods: {
paginateBackIfNeeded() {
- this.$nextTick(() => {
- const container = this.$refs.chatContainer;
- if (container.scrollHeight <= container.clientHeight) {
- this.handleScrolledToTop();
- }
- })
+ this.$nextTick(() => {
+ const container = this.$refs.chatContainer;
+ if (container.scrollHeight <= container.clientHeight) {
+ this.handleScrolledToTop();
+ }
+ });
},
onScroll(ignoredevent) {
const container = this.$refs.chatContainer;
@@ -235,7 +287,7 @@ export default {
container.scrollHeight - container.scrollTop ==
container.clientHeight
) {
- this.handleScrolledToBottom();
+ this.handleScrolledToBottom(false);
}
},
onEvent(event) {
@@ -243,6 +295,15 @@ export default {
return; // Not for this room
}
this.paginateBackIfNeeded();
+
+ // If we are at bottom, scroll to see new events...
+ const container = this.$refs.chatContainer;
+ if (
+ container.scrollHeight - container.scrollTop ==
+ container.clientHeight
+ ) {
+ this.handleScrolledToBottom(true);
+ }
},
onUserTyping(event) {
@@ -273,7 +334,13 @@ export default {
if (this.room) {
const member = this.room.getMember(event.getSender());
if (member) {
- return member.getAvatarUrl(this.$matrix.matrixClient.getHomeserverUrl(), 40, 40, "scale", true);
+ return member.getAvatarUrl(
+ this.$matrix.matrixClient.getHomeserverUrl(),
+ 40,
+ 40,
+ "scale",
+ true
+ );
}
}
return null;
@@ -286,6 +353,98 @@ export default {
}
},
+ /**
+ * Show attachment picker to select image
+ */
+ pickAttachment(event) {
+ if (event.target.files && event.target.files[0]) {
+ var reader = new FileReader();
+ reader.onload = (e) => {
+ this.currentImageInput = e.target.result;
+ this.currentImageInputPath = event.target.files[0];
+ };
+ reader.readAsDataURL(event.target.files[0]);
+ }
+ },
+
+ onUploadProgress(p) {
+ if (p.total) {
+ this.currentSendProgress =
+ "Uploaded " + (p.loaded || 0) + " of " + p.total;
+ } else {
+ this.currentSendProgress = "Uploaded " + (p.loaded || 0);
+ }
+ },
+
+ sendAttachment() {
+ if (this.currentImageInputPath) {
+ const opts = {
+ progressHandler: this.onUploadProgress,
+ };
+ this.currentSendOperation = this.$matrix.uploadFile(
+ this.currentImageInputPath,
+ opts
+ );
+ var matrixUri;
+ this.currentSendOperation
+ .then((uri) => {
+ matrixUri = uri;
+ return this.$matrix.matrixClient.sendImageMessage(
+ this.roomId,
+ matrixUri,
+ "",
+ "Image",
+ null
+ );
+ })
+ .then((result) => {
+ console.log("Image sent: ", result);
+ })
+ .catch((err) => {
+ console.log("Image send error: ", err);
+ if (err && err.name == "UnknownDeviceError") {
+ console.log("Unknown devices. Mark as known before retrying.");
+ var setAsKnownPromises = [];
+ for (var user of Object.keys(err.devices)) {
+ const userDevices = err.devices[user];
+ for (var deviceId of Object.keys(userDevices)) {
+ const deviceInfo = userDevices[deviceId];
+ if (!deviceInfo.known) {
+ setAsKnownPromises.push(
+ this.$matrix.matrixClient.setDeviceKnown(
+ user,
+ deviceId,
+ true
+ )
+ );
+ }
+ }
+ }
+ Promise.all(setAsKnownPromises)
+ .then(() => {
+ // All devices now marked as "known", try to resend
+ return this.$matrix.matrixClient.sendImageMessage(
+ this.roomId,
+ matrixUri,
+ "",
+ "Image",
+ null
+ );
+ })
+ .then((result) => {
+ console.log("Image sent: ", result);
+ })
+ .catch((err) => {
+ // Still error, abort
+ this.currentSendError = err.toLocaleString();
+ });
+ } else {
+ this.currentSendError = err.toLocaleString();
+ }
+ });
+ }
+ },
+
sendMatrixMessage(body) {
var content = {
body: body,
@@ -320,7 +479,6 @@ export default {
handleScrolledToTop() {
console.log("@top");
- // const room = this.$matrix.getRoom(this.roomId);
if (
this.timelineWindow &&
this.timelineWindow.canPaginate(EventTimeline.BACKWARDS)
@@ -341,8 +499,46 @@ export default {
}
},
- handleScrolledToBottom() {
+ handleScrolledToBottom(scrollToEnd) {
console.log("@bottom");
+ if (
+ this.timelineWindow &&
+ this.timelineWindow.canPaginate(EventTimeline.FORWARDS)
+ ) {
+ this.timelineWindow
+ .paginate(EventTimeline.FORWARDS, 10, true)
+ .then((success) => {
+ if (success) {
+ this.scrollPosition.prepareFor("down");
+ this.events = this.timelineWindow.getEvents();
+ this.$nextTick(() => {
+ // restore scroll position!
+ console.log("Restore scroll!");
+ this.scrollPosition.restore();
+ if (scrollToEnd) {
+ this.smoothScrollToEnd();
+ }
+ });
+ }
+ });
+ }
+ },
+
+ smoothScrollToEnd() {
+ this.$nextTick(function () {
+ const container = this.$refs.chatContainer;
+ if (container.children.length > 0) {
+ const lastChild = container.children[container.children.length - 1];
+ console.log("Scroll into view", lastChild);
+ window.requestAnimationFrame(() => {
+ lastChild.scrollIntoView({
+ behavior: "smooth",
+ block: "start",
+ inline: "nearest",
+ });
+ });
+ }
+ });
},
},
};
diff --git a/src/plugins/utils.tsx b/src/plugins/utils.tsx
new file mode 100644
index 0000000..0b18ab9
--- /dev/null
+++ b/src/plugins/utils.tsx
@@ -0,0 +1,16 @@
+class Util {
+ readFileAsArrayBuffer(file: File | Blob): Promise {
+ return new Promise((resolve, reject) => {
+ const reader = new FileReader();
+ reader.onload = function(e) {
+ resolve(e.target.result as ArrayBuffer);
+ };
+ reader.onerror = function(e) {
+ reject(e);
+ };
+ reader.readAsArrayBuffer(file);
+ })
+ };
+};
+export default new Util();
+
diff --git a/src/services/matrix.service.js b/src/services/matrix.service.js
index 2945c32..2bad182 100644
--- a/src/services/matrix.service.js
+++ b/src/services/matrix.service.js
@@ -195,7 +195,11 @@ export default {
if (this.matrixClient) {
this.matrixClient.off(event, handler);
}
- }
+ },
+
+ uploadFile(file, opts) {
+ return this.matrixClient.uploadContent(file, opts);
+ },
}
})