@@ -281,7 +287,7 @@
diff --git a/src/components/RoomTypeSelector.vue b/src/components/RoomTypeSelector.vue
new file mode 100644
index 0000000..6284d9b
--- /dev/null
+++ b/src/components/RoomTypeSelector.vue
@@ -0,0 +1,41 @@
+
+
+ {{ item.title }}
+
+
+
+ {{ item.title }}
+ {{ item.description }}
+
+
+
+
+
+
+
+
+
diff --git a/src/components/chatMixin.js b/src/components/chatMixin.js
index d1b0048..7d61e67 100644
--- a/src/components/chatMixin.js
+++ b/src/components/chatMixin.js
@@ -6,6 +6,7 @@ import MessageIncomingAudio from "./messages/MessageIncomingAudio.vue";
import MessageIncomingVideo from "./messages/MessageIncomingVideo.vue";
import MessageIncomingSticker from "./messages/MessageIncomingSticker.vue";
import MessageIncomingPoll from "./messages/MessageIncomingPoll.vue";
+import MessageIncomingThread from "./messages/MessageIncomingThread.vue";
import MessageOutgoingText from "./messages/MessageOutgoingText";
import MessageOutgoingFile from "./messages/MessageOutgoingFile";
import MessageOutgoingImage from "./messages/MessageOutgoingImage.vue";
@@ -58,6 +59,7 @@ export default {
MessageIncomingAudio,
MessageIncomingVideo,
MessageIncomingSticker,
+ MessageIncomingThread,
MessageOutgoingText,
MessageOutgoingFile,
MessageOutgoingImage,
@@ -152,7 +154,11 @@ export default {
case "m.room.message":
if (event.getSender() != this.$matrix.currentUserId) {
- if (event.getContent().msgtype == "m.image") {
+ if (event.isThreadRoot) {
+ // Incoming thread, e.g. a file drop!
+ return MessageIncomingThread;
+ }
+ if (event.getContent().msgtype == "m.image") {
// For SVG, make downloadable
if (
event.getContent().info &&
diff --git a/src/components/file_mode/FileDropLayout.vue b/src/components/file_mode/FileDropLayout.vue
new file mode 100644
index 0000000..3e09221
--- /dev/null
+++ b/src/components/file_mode/FileDropLayout.vue
@@ -0,0 +1,285 @@
+
+
+
+
+
+
$vuetify.icons.ic_lock
+
{{ $t("file_mode.secure_file_send") }}
+
+
+
{{ $t("file_mode.choose_files") }}
+
{{ $t("file_mode.any_file_format_accepted") }}
+
+
+
+
+
+
+
+
+
{{ attachments[currentItemIndex].name }}
+
+
+
+
+
+ $vuetify.icons.ic_trash
+
+
+
+
+
+
+ {{ $t("menu.send") }}
+
+
+
+
+
+
+
+
+
+
+
{{ $t('file_mode.sending_progress') }}
+
+
+
+
+
+ $vuetify.icons.ic_check_circle
+
+
+
+
+
+
+
+
{{ info.attachment.name }}
+
+
+ close
+
+
+
+
+
{{ $tc((this.messageInput && this.messageInput.length > 0) ?
+ "file_mode.files_sent_with_note" : "file_mode.files_sent", sentItems.length) }}
+
+
+
+
+
+
+
+
+ {{ $t("file_mode.sending") }}
+
+
+ {{ $t("file_mode.return_to_home") }}
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/components/messages/MessageIncomingThread.vue b/src/components/messages/MessageIncomingThread.vue
new file mode 100644
index 0000000..0ff7129
--- /dev/null
+++ b/src/components/messages/MessageIncomingThread.vue
@@ -0,0 +1,114 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ block
+ {{ $t('message.incoming_message_deleted_text') }}
+
+
+
+ {{ $t('message.edited') }}
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/components/roomInfoMixin.js b/src/components/roomInfoMixin.js
index 53cb205..f4c3657 100644
--- a/src/components/roomInfoMixin.js
+++ b/src/components/roomInfoMixin.js
@@ -1,3 +1,5 @@
+import utils from "../plugins/utils";
+
export default {
data() {
return {
@@ -57,7 +59,7 @@ export default {
publicRoomLink() {
if (this.room && this.roomJoinRule == "public") {
return this.$router.getRoomLink(
- this.room.getCanonicalAlias(), this.room.roomId, this.room.name
+ this.room.getCanonicalAlias(), this.room.roomId, this.room.name, utils.roomDisplayTypeToQueryParam(this.room)
);
}
return null;
diff --git a/src/components/roomTypeMixin.js b/src/components/roomTypeMixin.js
new file mode 100644
index 0000000..57ac6df
--- /dev/null
+++ b/src/components/roomTypeMixin.js
@@ -0,0 +1,24 @@
+import { ROOM_TYPE_VOICE_MODE, ROOM_TYPE_FILE_MODE, ROOM_TYPE_DEFAULT } from "../plugins/utils";
+
+export default {
+ computed: {
+ availableRoomTypes() {
+ let types = [{ title: this.$t("room_info.room_type_default"), description: "", value: ROOM_TYPE_DEFAULT }];
+ if (this.$config.experimental_voice_mode) {
+ types.push({
+ title: this.$t("room_info.voice_mode"),
+ description: this.$t("room_info.voice_mode_info"),
+ value: ROOM_TYPE_VOICE_MODE,
+ });
+ }
+ if (this.$config.experimental_file_mode) {
+ types.push({
+ title: this.$t("room_info.file_mode"),
+ description: this.$t("room_info.file_mode_info"),
+ value: ROOM_TYPE_FILE_MODE,
+ });
+ }
+ return types;
+ },
+ },
+};
diff --git a/src/plugins/utils.js b/src/plugins/utils.js
index d7ed570..320f6d8 100644
--- a/src/plugins/utils.js
+++ b/src/plugins/utils.js
@@ -3,7 +3,9 @@ import * as ContentHelpers from "matrix-js-sdk/lib/content-helpers";
import dataUriToBuffer from "data-uri-to-buffer";
import ImageResize from "image-resize";
+export const ROOM_TYPE_DEFAULT = "im.keanu.room_type_default";
export const ROOM_TYPE_VOICE_MODE = "im.keanu.room_type_voice";
+export const ROOM_TYPE_FILE_MODE = "im.keanu.room_type_file";
const sizeOf = require("image-size");
@@ -283,7 +285,7 @@ class Util {
matrixClient.sendEvent(roomId, eventType, content, undefined, undefined)
.then((result) => {
console.log("Message sent: ", result);
- resolve(true);
+ resolve(result.event_id);
})
.catch(err => {
console.log("Send error: ", err);
@@ -322,7 +324,7 @@ class Util {
matrixClient.resendEvent(event, matrixClient.getRoom(event.getRoomId()))
.then((result) => {
console.log("Message sent: ", result);
- resolve(true);
+ resolve(result.event_id);
})
.catch((err) => {
// Still error, abort
@@ -337,7 +339,7 @@ class Util {
});
}
- sendImage(matrixClient, roomId, file, onUploadProgress) {
+ sendImage(matrixClient, roomId, file, onUploadProgress, threadRoot) {
return new UploadPromise((resolve, reject, aborter) => {
const abortionController = aborter;
var reader = new FileReader();
@@ -382,6 +384,14 @@ class Util {
msgtype: msgtype
}
+ // If thread root (an eventId) is set, add that here
+ if (threadRoot) {
+ messageContent["m.relates_to"] = {
+ "rel_type": "m.thread",
+ "event_id": threadRoot
+ };
+ }
+
// Set filename for files
if (msgtype == 'm.file') {
messageContent.filename = file.name;
@@ -462,21 +472,29 @@ class Util {
}
/**
- * Return 'true' if we should use voice mode for the given room.
- *
+ * Return what "mode" to use for the given room.
+ *
* The default value is given by the room itself. If the "type" of the
* room is set to 'im.keanu.room_type_voice' then we default to voice mode,
- * else not. The user can then override this default by flipping the "voice mode"
- * swicth on room settings (it will be persisted as a user specific tag on the room)
+ * else if set to 'im.keanu.room_type_file' we default to file mode.
+ * The user can then override this default by changing the "room type"
+ * in room settings (it will be persisted as a user specific tag on the room)
*/
- useVoiceMode(roomOrNull) {
+ roomDisplayType(roomOrNull) {
if (roomOrNull) {
const room = roomOrNull;
// Have we changed our local view mode of this room?
const tags = room.tags;
if (tags && tags["ui_options"]) {
- return tags["ui_options"]["voice_mode"] === 1;
+ if (tags["ui_options"]["voice_mode"] === 1) {
+ return ROOM_TYPE_VOICE_MODE;
+ } else if (tags["ui_options"]["file_mode"] === 1) {
+ return ROOM_TYPE_FILE_MODE;
+ } else if (tags["ui_options"]["file_mode"] === 0 && tags["ui_options"]["file_mode"] === 0) {
+ // Explicitly set to "default"
+ return ROOM_TYPE_DEFAULT;
+ }
}
// Was the room created with a voice mode type?
@@ -485,10 +503,34 @@ class Util {
""
);
if (createEvent) {
- return createEvent.getContent().type === ROOM_TYPE_VOICE_MODE;
+ const roomType = createEvent.getContent().type;
+
+ // Validate value, or return default
+ if ([ROOM_TYPE_FILE_MODE, ROOM_TYPE_VOICE_MODE].includes(roomType)) {
+ return roomType;
+ }
}
}
- return false;
+ return ROOM_TYPE_DEFAULT;
+ }
+
+ /**
+ * Return the room type for the current room
+ * @param {*} roomOrNull
+ */
+ roomDisplayTypeToQueryParam(roomOrNull) {
+ const roomType = this.roomDisplayType(roomOrNull);
+ if (roomType === ROOM_TYPE_FILE_MODE) {
+ // Send "file" here, so the receiver of the invite link knows to display the "file drop" join page
+ // instead of the standard one.
+ return "file";
+ } else if (roomType === ROOM_TYPE_VOICE_MODE) {
+ // No need to return "voice" here. The invite page looks the same for default and voice mode,
+ // so currently no point in cluttering the invite link with it. The corrent mode will be picked up from
+ // room creation flags once the user joins.
+ return undefined;
+ }
+ return undefined; // Default, just return undefined
}
/** Generate a random user name */
@@ -592,7 +634,7 @@ class Util {
findOneVisibleElement(parentNode) {
let start = 0;
- let end = parentNode.children.length - 1;
+ let end = (parentNode && parentNode.children) ? parentNode.children.length - 1 : -1;
while (start <= end) {
let middle = Math.floor((start + end) / 2);
let childNode = parentNode.children[middle];
diff --git a/src/router/index.js b/src/router/index.js
index 7c85163..ce5a865 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -140,11 +140,22 @@ router.beforeEach((to, from, next) => {
}
});
-router.getRoomLink = function (alias, roomId, roomName) {
+router.getRoomLink = function (alias, roomId, roomName, mode) {
+ let params = {};
if ((!alias || roomName.replace(/\s/g, "").toLowerCase() !== util.getRoomNameFromAlias(alias)) && roomName) {
// There is no longer a correlation between alias and room name, probably because room name has
// changed. Include the "?roomName" part
- return window.location.origin + window.location.pathname + "?roomName=" + encodeURIComponent(roomName) + "#/room/" + encodeURIComponent(util.sanitizeRoomId(alias || roomId));
+ params["roomName"] = roomName;
+ }
+ if (mode) {
+ // Optional mode given, append as "m" query param
+ params["m"] = mode;
+ }
+ if (Object.entries(params).length > 0) {
+ const queryString = Object.entries(params)
+ .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
+ .join('&')
+ return window.location.origin + window.location.pathname + "?" + queryString + "#/room/" + encodeURIComponent(util.sanitizeRoomId(alias || roomId));
}
return window.location.origin + window.location.pathname + "#/room/" + encodeURIComponent(util.sanitizeRoomId(alias || roomId));
}