diff --git a/src/api/datatransfermodel.h b/src/api/datatransfermodel.h index 250f6dfd4cb70699d7202134466914e0a378c187..9394cf51c92e06399042a407570bdcc9e6c37b5e 100644 --- a/src/api/datatransfermodel.h +++ b/src/api/datatransfermodel.h @@ -77,6 +77,8 @@ public: const QString& fileId, const QString& path); + void copyTo(const QString& accountId, const QString& convId, const QString& interactionId, const QString& destPath); + void cancel(const QString& accountId, const QString& conversationId, const QString& fileId); void registerTransferId(const QString& fileId, const QString& interactionId); diff --git a/src/chatview.cpp b/src/chatview.cpp index 57f3d5abe07f13093bc7ef7bb683c32526e076e9..536394b3435faf1a0b9db3dcaeb1218ecae18175 100644 --- a/src/chatview.cpp +++ b/src/chatview.cpp @@ -41,6 +41,7 @@ getTranslatedStrings(bool qwebview) {"Leave video message", QObject::tr("Leave video message")}, {"Leave audio message", QObject::tr("Leave audio message")}, {"Block", QObject::tr("Block")}, + {"Copy to downloads", QObject::tr("Copy to downloads")}, {"Write to {0}", QObject::tr("Write to {0}")}, {"Note: an interaction will create a new contact.", QObject::tr("Note: an interaction will create a new contact.")}, diff --git a/src/datatransfermodel.cpp b/src/datatransfermodel.cpp index 146f6ba0249628b277de5c47f28739c5445310cc..2df7baf0c24ae0609b5f2b1e8d057b51ce2f37d4 100644 --- a/src/datatransfermodel.cpp +++ b/src/datatransfermodel.cpp @@ -79,7 +79,7 @@ class DataTransferModel::Impl : public QObject public: Impl(DataTransferModel& up_link); - QString getUniqueFilePath(const QString& filename); + QString getUniqueFilePath(const QString& filename, const QString& path = ""); DataTransferModel& upLink; MapStringString file2InteractionId; @@ -92,16 +92,21 @@ DataTransferModel::Impl::Impl(DataTransferModel& up_link) {} QString -DataTransferModel::Impl::getUniqueFilePath(const QString& filename) +DataTransferModel::Impl::getUniqueFilePath(const QString& filename, const QString& path) { - if (!QFile::exists(filename)) { - return filename; - } - QString base(filename); - QString ext = QFileInfo(filename).completeSuffix(); + auto wantedDest = filename; + QString base(wantedDest); + QString ext = QFileInfo(wantedDest).completeSuffix(); if (!ext.isEmpty()) { ext = ext.prepend("."); } + if (!path.isEmpty()) { + QFileInfo fi(filename); + wantedDest = QDir(path).filePath(fi.baseName() + ext); + } + if (!QFile::exists(wantedDest)) { + return wantedDest; + } base.chop(ext.size()); QString ret; for (int suffix = 1;; suffix++) { @@ -202,7 +207,7 @@ QString DataTransferModel::accept(const QString& accountId, const QString& fileId, const QString& file_path, - std::size_t offset) + std::size_t) { auto unique_file_path = pimpl_->getUniqueFilePath(file_path); auto dring_id = pimpl_->interactionToFileId[fileId]; @@ -220,12 +225,35 @@ DataTransferModel::download(const QString& accountId, ConfigurationManager::instance().downloadFile(accountId, convId, interactionId, fileId, path); } +void +DataTransferModel::copyTo(const QString& accountId, const QString& convId, const QString& interactionId, const QString& destPath) +{ + auto fileId = getFileIdFromInteractionId(interactionId); + if (fileId.isEmpty()) { + qWarning() << "Cannot find any file for " << interactionId; + return; + } + QString path; + qlonglong total, progress; + + fileTransferInfo(accountId, convId, fileId, path, total, progress); + + auto src = QFile(path); + auto srcfi = QFileInfo(path); + if (!src.exists()) + return; + + auto realPath = srcfi.isSymLink() ? srcfi.symLinkTarget() : path; + auto dest = pimpl_->getUniqueFilePath(realPath, destPath); + src.copy(dest); +} + + void DataTransferModel::cancel(const QString& accountId, const QString& conversationId, const QString& interactionId) { - qWarning() << "@@@ " << accountId << " - " << conversationId << " - " << interactionId; ConfigurationManager::instance().cancelDataTransfer(accountId, conversationId, getFileIdFromInteractionId(interactionId)); diff --git a/src/web-chatview/chatview.css b/src/web-chatview/chatview.css index 93dc8c4cba15be90cd1b8a852bb417a9aa11c0e4..b316af1c2e059aa52346ed5b00c9ca6b74c556d1 100644 --- a/src/web-chatview/chatview.css +++ b/src/web-chatview/chatview.css @@ -831,6 +831,37 @@ input[type=checkbox] { opacity: 1; } + +.message_type_call .menu_interaction .dropdown +.message_type_contact .menu_interaction .dropdown +{ + margin-top: -17px; +} + +.dropdown { + display: none; + z-index: 1; + position: absolute; + background-color: #fff; + padding-top: 3px; + padding-bottom: 3px; +} + +.dropdown div +{ + color: #111; + padding: 10px; +} + +.dropdown div:hover +{ + background-color: #ddd; +} + +.showmenu:checked ~ .dropdown{ + display: block; +} + /* Buttons */ .flat-button { diff --git a/src/web-chatview/chatview.js b/src/web-chatview/chatview.js index 6de8f7b751dc1f6b34e1340f979da80cfc142425..3ff25767d471a8487b284106c83cded4e1f09aff 100644 --- a/src/web-chatview/chatview.js +++ b/src/web-chatview/chatview.js @@ -446,7 +446,7 @@ function showInvitation(contactAlias, contactId, isSwarm, isSyncing) { invitationNoteText.style.visibility = "visible" messageBar.style.visibility = "visible" } - + invitation.style.display = "flex" invitation.style.visibility = "visible" @@ -1324,6 +1324,12 @@ function updateFileInteraction(message_div, message_object, forceTypeToFile = fa message_div.querySelector(".full").innerText = message_text message_div.querySelector(".filename").innerText = message_text.split("/").pop() updateProgressBar(message_div.querySelector(".message_progress_bar"), message_object) + + + if (delivery_status === "finished") { + var message_dropdown = buildMessageDropdown(message_id) + message_div.appendChild(message_dropdown) + } } /** @@ -1634,6 +1640,57 @@ function textInteraction(message_id, message_direction, htmlText) { return internal_mes_wrapper } +/** + * Build message dropdown + * @return a message dropdown for passed message id + */ +function buildMessageDropdown(message_id) { + const menu_element = document.createElement("div") + menu_element.setAttribute("class", "menu_interaction") + menu_element.innerHTML = + `<input type="checkbox" id="showmenu${message_id}" class="showmenu"> + <label for="showmenu${message_id}"> + <svg fill="#888888" height="12" viewBox="0 0 24 24" width="12" xmlns="http://www.w3.org/2000/svg"> + <path d="M0 0h24v24H0z" fill="none"/> + <path d="M6 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm12 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-6 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"/> + </svg> + </label>` + menu_element.onclick = function () { + const button = this.querySelector(".showmenu") + button.checked = !button.checked + } + menu_element.onmouseleave = function () { + const button = this.querySelector(".showmenu") + button.checked = false + } + const dropdown = document.createElement("div") + const dropdown_classes = [ + "dropdown", + `dropdown_${message_id}` + ] + dropdown.setAttribute("class", dropdown_classes.join(" ")) + + const save = document.createElement("div") + save.setAttribute("class", "menuoption") + if (use_qt) { + save.innerHTML = "Copy to downloads" + } else { + save.innerHTML = i18n.gettext("Copy to downloads") + } + save.msg_id = message_id + save.onclick = function () { + if (use_qt) { + window.jsbridge.copyToDownloads(`${this.msg_id}`) + } else { + window.prompt(`COPY:${this.msg_id}`) + } + } + dropdown.appendChild(save) + menu_element.appendChild(dropdown) + + return menu_element +} + /** * Update a text interaction (text) * @param message_div the message to update @@ -1832,6 +1889,10 @@ function buildNewMessage(message_object) { message_div.append(fileInteraction(message_id)) updateProgressBar(message_div.querySelector(".message_progress_bar"), message_object) } + if (delivery_status === "finished") { + var message_dropdown = buildMessageDropdown(message_id) + message_div.appendChild(message_dropdown) + } } else if (message_type === "text") { // TODO add the possibility to update messages (remove and rebuild) const htmlText = getMessageHtml(message_text)