feat(ConversationViewer): add functionality for sharing messages as paper messages, implement translation feature, and enhance message input with draft saving and dynamic height adjustment
This commit is contained in:
@@ -463,6 +463,15 @@
|
|||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<!-- share as paper message -->
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="inline-flex items-center gap-x-1.5 rounded-lg bg-blue-500 px-3 py-1.5 text-xs font-semibold text-white shadow-sm hover:bg-blue-600 transition-colors focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 ml-2"
|
||||||
|
@click.stop="shareAsPaperMessage(chatItem)"
|
||||||
|
>
|
||||||
|
Paper Message
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -752,8 +761,9 @@
|
|||||||
ref="message-input"
|
ref="message-input"
|
||||||
v-model="newMessageText"
|
v-model="newMessageText"
|
||||||
:readonly="isSendingMessage"
|
:readonly="isSendingMessage"
|
||||||
class="bg-white dark:bg-zinc-900 border border-gray-200 dark:border-zinc-800 text-gray-900 dark:text-zinc-100 text-sm rounded-xl focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 block w-full px-3 sm:px-4 py-2 resize-none shadow-sm transition-all placeholder:text-gray-400 dark:placeholder:text-zinc-500"
|
class="bg-white dark:bg-zinc-900 border border-gray-200 dark:border-zinc-800 text-gray-900 dark:text-zinc-100 text-sm rounded-xl focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 block w-full px-3 sm:px-4 py-2 resize-none shadow-sm transition-all placeholder:text-gray-400 dark:placeholder:text-zinc-500 min-h-[40px] max-h-[200px] overflow-y-auto"
|
||||||
rows="2"
|
rows="1"
|
||||||
|
spellcheck="true"
|
||||||
:placeholder="$t('messages.send_placeholder')"
|
:placeholder="$t('messages.send_placeholder')"
|
||||||
@keydown.enter.exact.prevent="onEnterPressed"
|
@keydown.enter.exact.prevent="onEnterPressed"
|
||||||
@keydown.enter.shift.exact.prevent="onShiftEnterPressed"
|
@keydown.enter.shift.exact.prevent="onShiftEnterPressed"
|
||||||
@@ -791,6 +801,16 @@
|
|||||||
<MaterialDesignIcon icon-name="crosshairs-question" class="w-4 h-4" />
|
<MaterialDesignIcon icon-name="crosshairs-question" class="w-4 h-4" />
|
||||||
<span class="hidden sm:inline">{{ $t("messages.request") }}</span>
|
<span class="hidden sm:inline">{{ $t("messages.request") }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="hasTranslator && newMessageText"
|
||||||
|
type="button"
|
||||||
|
class="attachment-action-button"
|
||||||
|
:title="$t('translator.translate')"
|
||||||
|
@click="translateMessage"
|
||||||
|
>
|
||||||
|
<MaterialDesignIcon icon-name="translate" class="w-4 h-4" />
|
||||||
|
<span class="hidden sm:inline">{{ $t("translator.translate") }}</span>
|
||||||
|
</button>
|
||||||
<div class="ml-auto my-auto">
|
<div class="ml-auto my-auto">
|
||||||
<SendMessageButton
|
<SendMessageButton
|
||||||
:is-sending-message="isSendingMessage"
|
:is-sending-message="isSendingMessage"
|
||||||
@@ -885,6 +905,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
|
||||||
|
<PaperMessageModal
|
||||||
|
v-if="isPaperMessageModalOpen"
|
||||||
|
:message-hash="paperMessageHash"
|
||||||
|
@close="isPaperMessageModalOpen = false"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -905,6 +931,7 @@ import AddImageButton from "./AddImageButton.vue";
|
|||||||
import IconButton from "../IconButton.vue";
|
import IconButton from "../IconButton.vue";
|
||||||
import GlobalEmitter from "../../js/GlobalEmitter";
|
import GlobalEmitter from "../../js/GlobalEmitter";
|
||||||
import ToastUtils from "../../js/ToastUtils";
|
import ToastUtils from "../../js/ToastUtils";
|
||||||
|
import PaperMessageModal from "./PaperMessageModal.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "ConversationViewer",
|
name: "ConversationViewer",
|
||||||
@@ -915,6 +942,7 @@ export default {
|
|||||||
MaterialDesignIcon,
|
MaterialDesignIcon,
|
||||||
SendMessageButton,
|
SendMessageButton,
|
||||||
AddAudioButton,
|
AddAudioButton,
|
||||||
|
PaperMessageModal,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
myLxmfAddressHash: {
|
myLxmfAddressHash: {
|
||||||
@@ -982,6 +1010,10 @@ export default {
|
|||||||
0x08: "2400", // AM_CODEC2_2400
|
0x08: "2400", // AM_CODEC2_2400
|
||||||
0x09: "3200", // AM_CODEC2_3200
|
0x09: "3200", // AM_CODEC2_3200
|
||||||
},
|
},
|
||||||
|
isPaperMessageModalOpen: false,
|
||||||
|
paperMessageHash: null,
|
||||||
|
hasTranslator: false,
|
||||||
|
translatorLanguages: [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -1042,9 +1074,15 @@ export default {
|
|||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
selectedPeer: {
|
selectedPeer: {
|
||||||
handler() {
|
handler(newPeer, oldPeer) {
|
||||||
|
if (oldPeer) {
|
||||||
|
this.saveDraft(oldPeer.destination_hash);
|
||||||
|
}
|
||||||
this.checkIfSelectedPeerBlocked();
|
this.checkIfSelectedPeerBlocked();
|
||||||
this.initialLoad();
|
this.initialLoad();
|
||||||
|
if (newPeer) {
|
||||||
|
this.loadDraft(newPeer.destination_hash);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
immediate: true,
|
immediate: true,
|
||||||
},
|
},
|
||||||
@@ -1052,6 +1090,11 @@ export default {
|
|||||||
// chat items for selected peer changed, so lets process any available audio
|
// chat items for selected peer changed, so lets process any available audio
|
||||||
await this.processAudioForSelectedPeerChatItems();
|
await this.processAudioForSelectedPeerChatItems();
|
||||||
},
|
},
|
||||||
|
newMessageText() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.adjustTextareaHeight();
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
// stop listening for websocket messages
|
// stop listening for websocket messages
|
||||||
@@ -1067,6 +1110,9 @@ export default {
|
|||||||
|
|
||||||
// load blocked destinations
|
// load blocked destinations
|
||||||
this.loadBlockedDestinations();
|
this.loadBlockedDestinations();
|
||||||
|
|
||||||
|
// check translator
|
||||||
|
this.checkTranslator();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async loadBlockedDestinations() {
|
async loadBlockedDestinations() {
|
||||||
@@ -1078,6 +1124,48 @@ export default {
|
|||||||
console.log(e);
|
console.log(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async checkTranslator() {
|
||||||
|
try {
|
||||||
|
const response = await window.axios.get("/api/v1/translator/languages");
|
||||||
|
this.translatorLanguages = response.data.languages || [];
|
||||||
|
this.hasTranslator = this.translatorLanguages.length > 0;
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Failed to check translator:", e);
|
||||||
|
this.hasTranslator = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async translateMessage() {
|
||||||
|
if (!this.newMessageText || this.isSendingMessage) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.isSendingMessage = true;
|
||||||
|
const targetLang = this.$i18n.locale || "en";
|
||||||
|
const response = await window.axios.post("/api/v1/translator/translate", {
|
||||||
|
text: this.newMessageText,
|
||||||
|
source_lang: "auto",
|
||||||
|
target_lang: targetLang,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.data.translated_text) {
|
||||||
|
this.newMessageText = response.data.translated_text;
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.adjustTextareaHeight();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Translation failed:", e);
|
||||||
|
ToastUtils.error("Translation failed");
|
||||||
|
} finally {
|
||||||
|
this.isSendingMessage = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
adjustTextareaHeight() {
|
||||||
|
const textarea = this.$refs["message-input"];
|
||||||
|
if (textarea) {
|
||||||
|
textarea.style.height = "auto";
|
||||||
|
textarea.style.height = Math.min(textarea.scrollHeight, 200) + "px";
|
||||||
|
}
|
||||||
|
},
|
||||||
checkIfSelectedPeerBlocked() {
|
checkIfSelectedPeerBlocked() {
|
||||||
if (!this.selectedPeer) {
|
if (!this.selectedPeer) {
|
||||||
this.isSelectedPeerBlocked = false;
|
this.isSelectedPeerBlocked = false;
|
||||||
@@ -1087,6 +1175,30 @@ export default {
|
|||||||
(b) => b.destination_hash === this.selectedPeer.destination_hash
|
(b) => b.destination_hash === this.selectedPeer.destination_hash
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
loadDraft(destinationHash) {
|
||||||
|
try {
|
||||||
|
const drafts = JSON.parse(localStorage.getItem("meshchat.drafts") || "{}");
|
||||||
|
this.newMessageText = drafts[destinationHash] || "";
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.adjustTextareaHeight();
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to load draft:", e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
saveDraft(destinationHash) {
|
||||||
|
try {
|
||||||
|
const drafts = JSON.parse(localStorage.getItem("meshchat.drafts") || "{}");
|
||||||
|
if (this.newMessageText) {
|
||||||
|
drafts[destinationHash] = this.newMessageText;
|
||||||
|
} else {
|
||||||
|
delete drafts[destinationHash];
|
||||||
|
}
|
||||||
|
localStorage.setItem("meshchat.drafts", JSON.stringify(drafts));
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to save draft:", e);
|
||||||
|
}
|
||||||
|
},
|
||||||
close() {
|
close() {
|
||||||
this.$emit("close");
|
this.$emit("close");
|
||||||
},
|
},
|
||||||
@@ -1713,6 +1825,10 @@ export default {
|
|||||||
this.isShareContactModalOpen = false;
|
this.isShareContactModalOpen = false;
|
||||||
await this.sendMessage();
|
await this.sendMessage();
|
||||||
},
|
},
|
||||||
|
shareAsPaperMessage(chatItem) {
|
||||||
|
this.paperMessageHash = chatItem.lxmf_message.hash;
|
||||||
|
this.isPaperMessageModalOpen = true;
|
||||||
|
},
|
||||||
async sendMessage() {
|
async sendMessage() {
|
||||||
// do nothing if can't send message
|
// do nothing if can't send message
|
||||||
if (!this.canSendMessage) {
|
if (!this.canSendMessage) {
|
||||||
@@ -1814,6 +1930,7 @@ export default {
|
|||||||
|
|
||||||
// clear message inputs
|
// clear message inputs
|
||||||
this.newMessageText = "";
|
this.newMessageText = "";
|
||||||
|
this.saveDraft(this.selectedPeer.destination_hash);
|
||||||
this.newMessageImage = null;
|
this.newMessageImage = null;
|
||||||
this.newMessageImageUrl = null;
|
this.newMessageImageUrl = null;
|
||||||
this.newMessageAudio = null;
|
this.newMessageAudio = null;
|
||||||
|
|||||||
Reference in New Issue
Block a user