diff options
| author | Andrew Branson <andrew.branson@jolla.com> | 2026-02-12 15:13:50 +0100 |
|---|---|---|
| committer | Andrew Branson <andrew.branson@jolla.com> | 2026-02-12 15:45:59 +0100 |
| commit | 7e10e89d72cc3a2b5eb314aa76c340793e5e26dd (patch) | |
| tree | 7de1130148126ad29a75ec83166b4812440ca77a | |
| parent | bce74e963abeb96a9c335f5461611dee544abc4c (diff) | |
Add support for text and link sharing in Mastodon transfer plugin
10 files changed, 229 insertions, 35 deletions
@@ -32,9 +32,13 @@ Sailfish OS account integration for Mastodon. - Includes delegate/feed item QML and `MastodonPostsModel`. ### `transferengine-plugins/` -- Transfer Engine integration for Mastodon image sharing. +- Transfer Engine integration for Mastodon sharing. - `mastodonshareplugin/`: sharing method discovery + metadata. - `mastodontransferplugin/`: media upload + status creation. +- Single share UI entry: `MastodonSharePost.qml` handles both media and text/link posting. +- Supports: + - image sharing (`image/jpeg`, `image/png`) + - link/text sharing (`text/x-url`, `text/plain`) with title/link extraction from share resources. ### `icons/` - Mastodon SVG assets and `sailfish-svg2png` conversion setup. diff --git a/rpm/sailfish-account-mastodon.spec b/rpm/sailfish-account-mastodon.spec index e864e8a..a975327 100644 --- a/rpm/sailfish-account-mastodon.spec +++ b/rpm/sailfish-account-mastodon.spec @@ -92,7 +92,7 @@ fi %{_libdir}/nemo-transferengine/plugins/sharing/libmastodonshareplugin.so %{_libdir}/nemo-transferengine/plugins/transfer/libmastodontransferplugin.so -%{_datadir}/nemo-transferengine/plugins/sharing/MastodonShareImage.qml +%{_datadir}/nemo-transferengine/plugins/sharing/MastodonSharePost.qml %files -n sailfish-account-mastodon-ts-devel %{_datadir}/translations/source/settings-accounts-mastodon.ts diff --git a/transferengine-plugins/mastodonshareplugin/MastodonShareImage.qml b/transferengine-plugins/mastodonshareplugin/MastodonShareImage.qml deleted file mode 100644 index 56b4b4b..0000000 --- a/transferengine-plugins/mastodonshareplugin/MastodonShareImage.qml +++ /dev/null @@ -1,10 +0,0 @@ -import QtQuick 2.6 -import Sailfish.Silica 1.0 -import Sailfish.TransferEngine 1.0 - -ShareFilePreview { - id: root - - metadataStripped: true - descriptionPlaceholderText: qsTr("Write a post") -} diff --git a/transferengine-plugins/mastodonshareplugin/MastodonSharePost.qml b/transferengine-plugins/mastodonshareplugin/MastodonSharePost.qml new file mode 100644 index 0000000..f965209 --- /dev/null +++ b/transferengine-plugins/mastodonshareplugin/MastodonSharePost.qml @@ -0,0 +1,132 @@ +import QtQuick 2.6 +import Sailfish.Silica 1.0 +import Sailfish.Lipstick 1.0 +import Sailfish.TransferEngine 1.0 + +Item { + id: root + + property var shareAction + property string mimeType: { + if (shareAction && shareAction.mimeType) { + return shareAction.mimeType + } + if (shareAction && shareAction.resources + && shareAction.resources.length > 0 + && shareAction.resources[0] + && shareAction.resources[0].type) { + return shareAction.resources[0].type + } + return "" + } + property bool textShare: mimeType === "text/x-url" || mimeType === "text/plain" + + width: parent ? parent.width : 0 + height: previewLoader.item ? previewLoader.item.height : 0 + + Loader { + id: previewLoader + + anchors.fill: parent + sourceComponent: root.textShare ? postPreview : imagePreview + } + + Component { + id: imagePreview + + ShareFilePreview { + shareAction: root.shareAction + metadataStripped: true + descriptionPlaceholderText: qsTr("Write a post") + } + } + + Component { + id: postPreview + + SilicaFlickable { + id: postRoot + + width: parent.width + height: contentHeight + contentHeight: contentColumn.height + + Component.onCompleted: { + sailfishTransfer.loadConfiguration(root.shareAction.toConfiguration()) + statusTextField.forceActiveFocus() + statusTextField.cursorPosition = statusTextField.text.length + } + + SailfishTransfer { + id: sailfishTransfer + } + + Column { + id: contentColumn + + width: parent.width + + TextArea { + id: linkTextField + + width: parent.width + //% "Link" + label: qsTrId("sailfishshare-la-link") + placeholderText: label + visible: sailfishTransfer.content.type === "text/x-url" + text: sailfishTransfer.content.data || sailfishTransfer.content.status || "" + } + + TextArea { + id: statusTextField + + width: parent.width + //% "Status update" + label: qsTrId("sailfishshare-la-status_update") + placeholderText: label + text: { + var title = sailfishTransfer.content.name || sailfishTransfer.content.linkTitle || "" + if (linkTextField.visible) { + return title + } + var body = sailfishTransfer.content.data || sailfishTransfer.content.status || "" + if (title.length > 0 && body.length > 0) { + return title + ": " + body + } + return title + body + } + } + + SystemDialogIconButton { + id: postButton + + anchors.horizontalCenter: parent.horizontalCenter + width: parent.width / 2 + iconSource: "image://theme/icon-m-share" + bottomPadding: Theme.paddingLarge + _showPress: false + + //: Post a social network account status update + //% "Post" + text: qsTrId("sailfishshare-la-post_status") + + onClicked: { + var status = statusTextField.text || "" + var link = linkTextField.visible ? (linkTextField.text || "") : "" + if (link.length > 0 && status.indexOf(link) === -1) { + status = status.length > 0 ? (status + "\n" + link) : link + } + + sailfishTransfer.userData = { + "accountId": sailfishTransfer.transferMethodInfo.accountId, + "status": status + } + sailfishTransfer.mimeType = linkTextField.visible ? "text/x-url" : "text/plain" + sailfishTransfer.start() + root.shareAction.done() + } + } + } + } + } +} diff --git a/transferengine-plugins/mastodonshareplugin/mastodonplugininfo.cpp b/transferengine-plugins/mastodonshareplugin/mastodonplugininfo.cpp index bc66752..a9cc1b7 100644 --- a/transferengine-plugins/mastodonshareplugin/mastodonplugininfo.cpp +++ b/transferengine-plugins/mastodonshareplugin/mastodonplugininfo.cpp @@ -6,7 +6,9 @@ MastodonPluginInfo::MastodonPluginInfo() , m_mastodonShareServiceStatus(new MastodonShareServiceStatus(this)) { m_capabilities << QLatin1String("image/jpeg") - << QLatin1String("image/png"); + << QLatin1String("image/png") + << QLatin1String("text/x-url") + << QLatin1String("text/plain"); connect(m_mastodonShareServiceStatus, &MastodonShareServiceStatus::serviceReady, this, &MastodonPluginInfo::serviceReady); @@ -42,9 +44,8 @@ void MastodonPluginInfo::serviceReady() info.setMethodId(QLatin1String("Mastodon")); info.setMethodIcon(QLatin1String("image://theme/icon-l-mastodon")); - info.setShareUIPath(QLatin1String("/usr/share/nemo-transferengine/plugins/sharing/MastodonShareImage.qml")); + info.setShareUIPath(QLatin1String("/usr/share/nemo-transferengine/plugins/sharing/MastodonSharePost.qml")); info.setCapabilities(m_capabilities); - m_info << info; } diff --git a/transferengine-plugins/mastodonshareplugin/mastodonshareplugin.pro b/transferengine-plugins/mastodonshareplugin/mastodonshareplugin.pro index 1ab7c6c..0dd1443 100644 --- a/transferengine-plugins/mastodonshareplugin/mastodonshareplugin.pro +++ b/transferengine-plugins/mastodonshareplugin/mastodonshareplugin.pro @@ -20,7 +20,7 @@ target.path = $$[QT_INSTALL_LIBS]/nemo-transferengine/plugins/sharing OTHER_FILES += *.qml -shareui.files = MastodonShareImage.qml +shareui.files = MastodonSharePost.qml shareui.path = /usr/share/nemo-transferengine/plugins/sharing INSTALLS += target shareui diff --git a/transferengine-plugins/mastodontransferplugin/mastodonapi.cpp b/transferengine-plugins/mastodontransferplugin/mastodonapi.cpp index a4b40a9..c12182f 100644 --- a/transferengine-plugins/mastodontransferplugin/mastodonapi.cpp +++ b/transferengine-plugins/mastodontransferplugin/mastodonapi.cpp @@ -108,10 +108,26 @@ bool MastodonApi::uploadImage(const QString &filePath, return true; } -bool MastodonApi::postStatus(const QString &mediaId) +bool MastodonApi::postStatus(const QString &statusText, + const QString &apiHost, + const QString &accessToken) { - if (mediaId.isEmpty()) { - qWarning() << Q_FUNC_INFO << "media id is empty"; + m_apiHost = normalizeApiHost(apiHost); + m_accessToken = accessToken; + m_statusText = statusText; + + if (m_accessToken.isEmpty()) { + qWarning() << Q_FUNC_INFO << "missing access token"; + return false; + } + + return postStatusInternal(QString()); +} + +bool MastodonApi::postStatusInternal(const QString &mediaId) +{ + if (m_statusText.trimmed().isEmpty() && mediaId.isEmpty()) { + qWarning() << Q_FUNC_INFO << "status and media id are empty"; return false; } @@ -119,7 +135,9 @@ bool MastodonApi::postStatus(const QString &mediaId) if (!m_statusText.isEmpty()) { query.addQueryItem(QStringLiteral("status"), m_statusText); } - query.addQueryItem(QStringLiteral("media_ids[]"), mediaId); + if (!mediaId.isEmpty()) { + query.addQueryItem(QStringLiteral("media_ids[]"), mediaId); + } const QByteArray postData = query.query(QUrl::FullyEncoded).toUtf8(); @@ -202,7 +220,7 @@ void MastodonApi::finished() } } - if (!postStatus(mediaId)) { + if (!postStatusInternal(mediaId)) { qWarning() << Q_FUNC_INFO << "unable to create mastodon status"; emit transferError(); } diff --git a/transferengine-plugins/mastodontransferplugin/mastodonapi.h b/transferengine-plugins/mastodontransferplugin/mastodonapi.h index 0ec3653..772ce89 100644 --- a/transferengine-plugins/mastodontransferplugin/mastodonapi.h +++ b/transferengine-plugins/mastodontransferplugin/mastodonapi.h @@ -26,6 +26,9 @@ public: const QString &mimeType, const QString &apiHost, const QString &accessToken); + bool postStatus(const QString &statusText, + const QString &apiHost, + const QString &accessToken); void cancelUpload(); @@ -43,7 +46,7 @@ private Q_SLOTS: private: static QString normalizeApiHost(const QString &rawHost); - bool postStatus(const QString &mediaId); + bool postStatusInternal(const QString &mediaId); void finishTransfer(QNetworkReply::NetworkError error, int httpCode, const QByteArray &data); QMap<QNetworkReply*, API_CALL> m_replies; diff --git a/transferengine-plugins/mastodontransferplugin/mastodonuploader.cpp b/transferengine-plugins/mastodontransferplugin/mastodonuploader.cpp index d85e138..050df18 100644 --- a/transferengine-plugins/mastodontransferplugin/mastodonuploader.cpp +++ b/transferengine-plugins/mastodontransferplugin/mastodonuploader.cpp @@ -88,7 +88,16 @@ void MastodonUploader::startUploading() return; } - postImage(); + const QString mimeType = mediaItem()->value(MediaItem::MimeType).toString(); + if (mimeType.startsWith(QLatin1String("image/"))) { + postImage(); + } else if (mimeType.contains(QLatin1String("text/plain")) + || mimeType.contains(QLatin1String("text/x-url"))) { + postStatus(); + } else { + qWarning() << Q_FUNC_INFO << "Unsupported mime type:" << mimeType; + setStatus(MediaTransferInterface::TransferInterrupted); + } } void MastodonUploader::transferFinished() @@ -172,6 +181,53 @@ void MastodonUploader::postImage() m_filePath = sourceFile; } + ensureApi(); + + const bool ok = m_api->uploadImage(m_filePath, + mediaItem()->value(MediaItem::Description).toString(), + mediaItem()->value(MediaItem::MimeType).toString(), + m_accountDetails.apiHost, + m_accountDetails.accessToken); + if (ok) { + setStatus(MediaTransferInterface::TransferStarted); + } else { + setStatus(MediaTransferInterface::TransferInterrupted); + qWarning() << Q_FUNC_INFO << "Failed to upload image"; + } +} + +void MastodonUploader::postStatus() +{ + ensureApi(); + + const QVariantMap userData = mediaItem()->value(MediaItem::UserData).toMap(); + QString statusText = userData.value(QStringLiteral("status")).toString().trimmed(); + if (statusText.isEmpty()) { + statusText = mediaItem()->value(MediaItem::Description).toString().trimmed(); + } + if (statusText.isEmpty()) { + statusText = mediaItem()->value(MediaItem::ContentData).toString().trimmed(); + } + + if (statusText.isEmpty()) { + qWarning() << Q_FUNC_INFO << "Failed to resolve status text"; + setStatus(MediaTransferInterface::TransferInterrupted); + return; + } + + const bool ok = m_api->postStatus(statusText, + m_accountDetails.apiHost, + m_accountDetails.accessToken); + if (ok) { + setStatus(MediaTransferInterface::TransferStarted); + } else { + setStatus(MediaTransferInterface::TransferInterrupted); + qWarning() << Q_FUNC_INFO << "Failed to post status"; + } +} + +void MastodonUploader::ensureApi() +{ if (!m_api) { m_api = new MastodonApi(m_qnam, this); connect(m_api, &MastodonApi::transferProgressUpdated, @@ -185,16 +241,4 @@ void MastodonUploader::postImage() connect(m_api, &MastodonApi::credentialsExpired, this, &MastodonUploader::credentialsExpired); } - - const bool ok = m_api->uploadImage(m_filePath, - mediaItem()->value(MediaItem::Description).toString(), - mediaItem()->value(MediaItem::MimeType).toString(), - m_accountDetails.apiHost, - m_accountDetails.accessToken); - if (ok) { - setStatus(MediaTransferInterface::TransferStarted); - } else { - setStatus(MediaTransferInterface::TransferInterrupted); - qWarning() << Q_FUNC_INFO << "Failed to upload image"; - } } diff --git a/transferengine-plugins/mastodontransferplugin/mastodonuploader.h b/transferengine-plugins/mastodontransferplugin/mastodonuploader.h index b0ea263..a55c359 100644 --- a/transferengine-plugins/mastodontransferplugin/mastodonuploader.h +++ b/transferengine-plugins/mastodontransferplugin/mastodonuploader.h @@ -38,7 +38,9 @@ protected: void setStatus(MediaTransferInterface::TransferStatus status); private: + void ensureApi(); void postImage(); + void postStatus(); MastodonApi *m_api; MastodonShareServiceStatus *m_mastodonShareServiceStatus; |
