summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Branson <andrew.branson@jolla.com>2026-02-12 15:13:50 +0100
committerAndrew Branson <andrew.branson@jolla.com>2026-02-12 15:45:59 +0100
commit7e10e89d72cc3a2b5eb314aa76c340793e5e26dd (patch)
tree7de1130148126ad29a75ec83166b4812440ca77a
parentbce74e963abeb96a9c335f5461611dee544abc4c (diff)
Add support for text and link sharing in Mastodon transfer plugin
-rw-r--r--README.md6
-rw-r--r--rpm/sailfish-account-mastodon.spec2
-rw-r--r--transferengine-plugins/mastodonshareplugin/MastodonShareImage.qml10
-rw-r--r--transferengine-plugins/mastodonshareplugin/MastodonSharePost.qml132
-rw-r--r--transferengine-plugins/mastodonshareplugin/mastodonplugininfo.cpp7
-rw-r--r--transferengine-plugins/mastodonshareplugin/mastodonshareplugin.pro2
-rw-r--r--transferengine-plugins/mastodontransferplugin/mastodonapi.cpp28
-rw-r--r--transferengine-plugins/mastodontransferplugin/mastodonapi.h5
-rw-r--r--transferengine-plugins/mastodontransferplugin/mastodonuploader.cpp70
-rw-r--r--transferengine-plugins/mastodontransferplugin/mastodonuploader.h2
10 files changed, 229 insertions, 35 deletions
diff --git a/README.md b/README.md
index 2773c87..cfb2db2 100644
--- a/README.md
+++ b/README.md
@@ -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;