diff --git a/README.md b/README.md index 1b7ddd3..c764eca 100644 --- a/README.md +++ b/README.md @@ -39,3 +39,12 @@ npm run lint ### Customize configuration See [Configuration Reference](https://cli.vuejs.org/config/). + +``` + +## Theming + +# Sticker short codes - To enable sticker short codes, follow there steps: +* set "useShortCodeStickers" to true in config.json. +* Add your sticker pack folders (containing stickers) to /src/assets/stickers/ +* Create file /src/assets/stickers/order.txt that lists the folders names in order, one folder name per line. \ No newline at end of file diff --git a/src/assets/config.json b/src/assets/config.json index c74a46c..d6bad56 100644 --- a/src/assets/config.json +++ b/src/assets/config.json @@ -1,3 +1,4 @@ { - "defaultServer": "https://neo.keanu.im" + "defaultServer": "https://neo.keanu.im", + "useShortCodeStickers": false } diff --git a/src/assets/css/chat.scss b/src/assets/css/chat.scss index 04ad1bf..1133285 100644 --- a/src/assets/css/chat.scss +++ b/src/assets/css/chat.scss @@ -197,6 +197,15 @@ $admin-fg: white; width: 70%; max-width: 70%; } + .bubble.sticker-bubble { + padding: 0px; + overflow: hidden; + display: inline-block; + width: 30%; + max-width: 30%; + background-color: transparent; + border: 0px solid transparent !important; + } .avatar { display: inline-block; vertical-align: top !important; @@ -261,6 +270,13 @@ $admin-fg: white; border-radius: 10px 10px 0 10px; } } + .bubble.sticker-bubble { + padding: 0px; + display: inline-block; + width: 30%; + max-width: 30%; + background-color: transparent; + } .avatar { display: inline-block; vertical-align: bottom !important; diff --git a/src/components/BottomSheet.vue b/src/components/BottomSheet.vue new file mode 100644 index 0000000..c1be4f3 --- /dev/null +++ b/src/components/BottomSheet.vue @@ -0,0 +1,184 @@ + + + + + + + + + + + + + + + + + diff --git a/src/components/Chat.vue b/src/components/Chat.vue index 9e9c68d..fd82805 100644 --- a/src/components/Chat.vue +++ b/src/components/Chat.vue @@ -195,6 +195,19 @@ + + + face + + + + + @@ -320,11 +335,13 @@ import MessageIncomingFile from "./messages/MessageIncomingFile"; import MessageIncomingImage from "./messages/MessageIncomingImage.vue"; import MessageIncomingAudio from "./messages/MessageIncomingAudio.vue"; import MessageIncomingVideo from "./messages/MessageIncomingVideo.vue"; +import MessageIncomingSticker from "./messages/MessageIncomingSticker.vue"; import MessageOutgoingText from "./messages/MessageOutgoingText"; import MessageOutgoingFile from "./messages/MessageOutgoingFile"; import MessageOutgoingImage from "./messages/MessageOutgoingImage.vue"; import MessageOutgoingAudio from "./messages/MessageOutgoingAudio.vue"; import MessageOutgoingVideo from "./messages/MessageOutgoingVideo.vue"; +import MessageOutgoingSticker from "./messages/MessageOutgoingSticker.vue"; import ContactJoin from "./messages/ContactJoin.vue"; import ContactLeave from "./messages/ContactLeave.vue"; import ContactInvited from "./messages/ContactInvited.vue"; @@ -343,6 +360,10 @@ import VoiceRecorder from "./VoiceRecorder"; import RoomInfoBottomSheet from "./RoomInfoBottomSheet"; import CreatedRoomWelcomeHeader from "./CreatedRoomWelcomeHeader"; import MessageOperationsBottomSheet from './MessageOperationsBottomSheet'; +import stickers from '../plugins/stickers'; +import StickerPickerBottomSheet from './StickerPickerBottomSheet'; +import BottomSheet from './BottomSheet.vue'; +import config from "../assets/config"; const READ_RECEIPT_TIMEOUT = 5000; /* How long a message must have been visible before the read marker is updated */ const WINDOW_BUFFER_SIZE = 0.3 /** Relative window height of when we start paginating. Always keep this much loaded before and after our scroll position! */ @@ -384,11 +405,13 @@ export default { MessageIncomingImage, MessageIncomingAudio, MessageIncomingVideo, + MessageIncomingSticker, MessageOutgoingText, MessageOutgoingFile, MessageOutgoingImage, MessageOutgoingAudio, MessageOutgoingVideo, + MessageOutgoingSticker, ContactJoin, ContactLeave, ContactInvited, @@ -404,11 +427,14 @@ export default { VoiceRecorder, RoomInfoBottomSheet, CreatedRoomWelcomeHeader, - MessageOperationsBottomSheet + MessageOperationsBottomSheet, + StickerPickerBottomSheet, + BottomSheet, }, data() { - return { + return { + config: config, events: [], currentInput: "", typingMembers: [], @@ -820,6 +846,8 @@ export default { return MessageIncomingVideo; } else if (event.getContent().msgtype == "m.file") { return MessageIncomingFile; + } else if (stickers.isStickerShortcode(event.getContent().body)) { + return MessageIncomingSticker; } return MessageIncomingText; } else { @@ -831,6 +859,8 @@ export default { return MessageOutgoingVideo; } else if (event.getContent().msgtype == "m.file") { return MessageOutgoingFile; + } else if (stickers.isStickerShortcode(event.getContent().body)) { + return MessageOutgoingSticker; } return MessageOutgoingText; } @@ -980,6 +1010,10 @@ export default { } }, + showStickerPicker() { + this.$refs.stickerPickerSheet.open(); + }, + onUploadProgress(p) { if (p.total) { this.currentSendProgress = @@ -1200,6 +1234,10 @@ export default { }); }, + sendSticker(stickerShortCode) { + this.sendMessage(stickerShortCode); + }, + showContextMenuForEvent(e) { const event = e.event; const ref = this.$refs[event.getId()]; diff --git a/src/components/StickerPickerBottomSheet.vue b/src/components/StickerPickerBottomSheet.vue new file mode 100644 index 0000000..e7c11bc --- /dev/null +++ b/src/components/StickerPickerBottomSheet.vue @@ -0,0 +1,77 @@ + + + + + + + {{ pack }} + mdi-phone + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/messages/MessageIncomingSticker.vue b/src/components/messages/MessageIncomingSticker.vue new file mode 100644 index 0000000..f992c72 --- /dev/null +++ b/src/components/messages/MessageIncomingSticker.vue @@ -0,0 +1,49 @@ + + + + + {{ + messageEventDisplayName(event).substring(0, 1).toUpperCase() + }} + + + + + + + more_vert + + + {{ messageEventDisplayName(event) }} + + {{ formatTime(event.event.origin_server_ts) }} + + + + + + + + \ No newline at end of file diff --git a/src/components/messages/MessageOutgoingSticker.vue b/src/components/messages/MessageOutgoingSticker.vue new file mode 100644 index 0000000..2063de0 --- /dev/null +++ b/src/components/messages/MessageOutgoingSticker.vue @@ -0,0 +1,47 @@ + + + + more_vert + + + + + + + + {{ userAvatarLetter }} + + + + + {{ formatTime(event.event.origin_server_ts) }} + {{ event.status }} + + + + + + + + \ No newline at end of file diff --git a/src/plugins/stickers.js b/src/plugins/stickers.js new file mode 100644 index 0000000..0ae5085 --- /dev/null +++ b/src/plugins/stickers.js @@ -0,0 +1,66 @@ +const stickerPacks = {}; +stickerPacks.ordering = []; +const stickerCodeMap = {}; + +try { + const stickerPackInfo = require("!!raw-loader!@/assets/stickers/order.txt").default; + const packInfo = stickerPackInfo.split("\n"); + for (let i = 0; i < packInfo.length; i++) { + const pack = packInfo[i]; + if (pack && pack.length > 0) { + stickerPacks[pack] = []; + stickerPacks.ordering.push(pack); + } + } +} catch (ignorederr) { + // +} + +function importAll(r) { + return r.keys().map(res => { + // Remove"./" + const parts = res.split("/"); + const pack = parts[1]; + const sticker = parts[2].split(".")[0]; + const image = r(res); + if (stickerPacks[pack] !== undefined) { + stickerPacks[pack].push({ image: image, name: sticker }); + stickerCodeMap[":" + pack + "-" + sticker + ":"] = image; + } + }); +} +importAll(require.context('@/assets/stickers/', true, /\.png$/)); + +class Stickers { + constructor() { + } + + isStickerShortcode(messageBody) { + if (messageBody && messageBody.startsWith(":") && messageBody.startsWith(":") && messageBody.length >= 5) { + const image = this.getStickerImage(messageBody); + return image != undefined && image != null; + } + return false; + } + + getStickerShortcode(pack, sticker) { + return ":" + pack + "-" + sticker.name + ":"; + } + + getPacks() { + return stickerPacks.ordering; + } + + getStickerImage(messageBody) { + if (!messageBody) return null; + if (messageBody.length < 5 || !messageBody.startsWith(":") || !messageBody.endsWith(":")) return null; + return stickerCodeMap[messageBody]; + } + + stickersInPack(pack) { + return stickerPacks[pack]; + } +} + +var gStickers = new Stickers(); +export default gStickers;