Skip to content

Commit f7bb33c

Browse files
committed
feat: Add link preview
1 parent 8dea928 commit f7bb33c

15 files changed

Lines changed: 465 additions & 51 deletions

src/server/CMakeLists.txt

Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ if (USE_SYSTEM_KF6)
1010
find_package(KF6SyntaxHighlighting REQUIRED)
1111
endif()
1212

13-
list(APPEND LIBS
13+
list(APPEND LIBS
1414
Qt6::Sql Qt6::Network Qt6::Svg Qt6::DBus Qt6::Concurrent Qt6::Quick Qt6::Qml
1515
Qt6::GuiPrivate # for deeper integration with wayland protocols, we need wl_surface
1616
Qt6::QuickDialogs2 Qt6::QuickControls2
1717
${CMARK_LIBRARY} ${CMARK_EXT_LIBRARY}
1818
minizip
19-
OpenSSL::Crypto
20-
wayland-client
19+
OpenSSL::Crypto
20+
wayland-client
2121
qt6keychain
2222
LibXml2::LibXml2
2323
KF6::SyntaxHighlighting
@@ -102,6 +102,8 @@ set(SRCS
102102
include/favicon/cached-favicon-request.hpp
103103
src/favicon/cached-favicon-request.cpp
104104

105+
include/url-metadata/url-metadata-service.hpp
106+
src/url-metadata/url-metadata-service.cpp
105107

106108
src/command-actions.cpp
107109

@@ -111,7 +113,7 @@ set(SRCS
111113
src/root-search/shortcuts/shortcut-root-provider.cpp
112114
src/root-search/apps/app-root-provider.hpp
113115
src/root-search/apps/app-root-provider.cpp
114-
116+
115117
src/extension/manager/extension-manager.hpp
116118
src/extension/manager/extension-manager.cpp
117119

@@ -134,7 +136,7 @@ set(SRCS
134136

135137
src/actions/app/app-actions.hpp
136138
src/actions/app/app-actions.cpp
137-
139+
138140
src/service-registry.cpp
139141

140142
src/services/background-effect/ext-background-effect-v1-manager.cpp
@@ -182,7 +184,6 @@ set(SRCS
182184

183185
src/lib/crypto.cpp
184186

185-
186187
include/extension/extension-command.hpp
187188

188189
src/extension/extension-action-panel-builder.hpp
@@ -194,8 +195,6 @@ set(SRCS
194195
include/extension/extension-command.hpp
195196
src/extension/extension-command.cpp
196197

197-
198-
199198
src/ui/omni-painter/omni-painter.hpp
200199
src/ui/omni-painter/omni-painter.cpp
201200

@@ -216,15 +215,12 @@ set(SRCS
216215

217216
include/command-database.hpp
218217
src/command-database.cpp
219-
220-
src/ui/image/url.hpp
221-
218+
src/ui/image/url.hpp
222219

223220
include/create-quicklink-command.hpp
224221

225222
src/builtin_icon.cpp
226223

227-
228224
src/extend/accessory-model.cpp
229225
src/extend/action-shortcut-parser.cpp
230226
src/extend/action-model.cpp
@@ -243,14 +239,11 @@ set(SRCS
243239

244240
src/image-fetcher.hpp
245241

246-
247242
src/command.hpp
248-
249243

250244
src/vicinae.hpp
251245
src/vicinae.cpp
252246

253-
254247
${EXTRA_PATH}/extension-boilerplate/boilerplate.qrc
255248

256249
./icons/icons.qrc
@@ -314,7 +307,6 @@ set(SRCS
314307

315308
src/cli/server.cpp
316309

317-
318310
src/utils/utils.cpp
319311

320312
src/services/file-chooser/abstract-file-chooser.hpp
@@ -327,15 +319,13 @@ set(SRCS
327319
src/services/root-item-manager/root-item-manager.hpp
328320
src/services/root-item-manager/root-item-manager.cpp
329321
src/services/root-item-manager/visit-tracker.cpp
330-
322+
331323
src/services/app-service/app-service.hpp
332324
src/services/app-service/app-service.cpp
333325

334326
src/services/emoji-service/emoji-service.hpp
335327
src/services/emoji-service/emoji-service.cpp
336328

337-
338-
339329
src/services/calculator-service/calculator-service.hpp
340330
src/services/calculator-service/calculator-service.cpp
341331

@@ -405,7 +395,6 @@ set(SRCS
405395

406396
src/overlay-controller/overlay-controller.hpp
407397

408-
409398
src/lib/zip/unzip.cpp
410399
src/lib/data-uri/data-uri.cpp
411400
src/lib/pid-file/pid-file.cpp
@@ -692,7 +681,6 @@ if (UNIX AND NOT APPLE)
692681

693682
wayland_generate_protocol("wlr-data-control-unstable-v1")
694683
endif()
695-
696684

697685
qt_add_executable(${TARGET} ${SRCS})
698686

@@ -733,6 +721,7 @@ set(VICINAE_QML_FILES
733721
src/qml/qml/SearchableDropdown.qml
734722
src/qml/qml/ClipboardHistoryView.qml
735723
src/qml/qml/ClipboardFilterAccessory.qml
724+
src/qml/qml/LinkPreview.qml
736725
src/qml/qml/DetailPanel.qml
737726
src/qml/qml/MetadataBar.qml
738727
src/qml/qml/AlertDialog.qml
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<RCC>
22
<qresource prefix="database/clipboard">
33
<file>migrations/001_init.sql</file>
4+
<file>migrations/002_add_url_metadata.sql</file>
45
</qresource>
56
</RCC>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
ALTER TABLE selection ADD COLUMN og_title TEXT;
2+
ALTER TABLE selection ADD COLUMN og_description TEXT;
3+
ALTER TABLE selection ADD COLUMN og_image TEXT;
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#pragma once
2+
#include <QObject>
3+
#include <QString>
4+
#include <QUrl>
5+
#include <optional>
6+
#include <unordered_map>
7+
#include <vector>
8+
9+
class QNetworkReply;
10+
11+
class UrlMetadataService : public QObject {
12+
Q_OBJECT
13+
14+
public:
15+
struct UrlMetadata {
16+
std::optional<QString> ogTitle;
17+
std::optional<QString> ogDescription;
18+
std::optional<QString> ogImage;
19+
};
20+
21+
explicit UrlMetadataService(QObject *parent = nullptr);
22+
~UrlMetadataService() override;
23+
24+
void fetchMetadata(const QString &selectionId, const QUrl &url);
25+
26+
signals:
27+
void metadataReady(const QString &selectionId, const QString &ogTitle, const QString &ogDescription,
28+
const QString &ogImage);
29+
30+
private:
31+
static constexpr size_t MAX_CACHE_SIZE = 200;
32+
static constexpr qint64 MAX_BUFFER_SIZE = 64 * 1024;
33+
static constexpr int TIMEOUT_MS = 10000;
34+
35+
static UrlMetadata parseMetaTags(const QByteArray &html);
36+
void abortAll();
37+
38+
struct InFlightRequest {
39+
QNetworkReply *reply;
40+
QByteArray buffer;
41+
std::vector<QString> selectionIds;
42+
};
43+
44+
std::unordered_map<QString, InFlightRequest> m_inFlight;
45+
std::unordered_map<QString, UrlMetadata> m_cache;
46+
};

src/server/src/extensions/clipboard/history/clipboard-history-controller.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ ClipboardHistoryController::ClipboardHistoryController(ClipboardService *clipboa
1414
&ClipboardHistoryController::handleClipboardChanged);
1515
connect(clipboard, &ClipboardService::selectionUpdated, this,
1616
&ClipboardHistoryController::handleClipboardChanged);
17+
connect(clipboard, &ClipboardService::selectionMetadataUpdated, this,
18+
&ClipboardHistoryController::handleClipboardChanged);
1719
}
1820

1921
void ClipboardHistoryController::setFilter(const QString &query) {

src/server/src/qml/clipboard-history-model.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,16 @@ QVariant ClipboardHistoryModel::data(const QModelIndex &index, int role) const {
4141

4242
QString ClipboardHistoryModel::itemId(int, int i) const { return m_entries[i].id; }
4343

44-
QString ClipboardHistoryModel::itemTitle(int, int i) const { return m_entries[i].textPreview; }
44+
QString ClipboardHistoryModel::itemTitle(int, int i) const {
45+
const auto &entry = m_entries[i];
46+
if (entry.ogTitle && !entry.ogTitle->isEmpty()) return *entry.ogTitle;
47+
return entry.textPreview;
48+
}
4549

4650
QString ClipboardHistoryModel::itemSubtitle(int, int i) const {
47-
auto dt = QDateTime::fromSecsSinceEpoch(m_entries[i].updatedAt);
51+
const auto &entry = m_entries[i];
52+
if (entry.ogDescription && !entry.ogDescription->isEmpty()) return *entry.ogDescription;
53+
auto dt = QDateTime::fromSecsSinceEpoch(entry.updatedAt);
4854
return getRelativeTimeString(dt);
4955
}
5056

src/server/src/qml/clipboard-history-view-host.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,10 +165,16 @@ void ClipboardHistoryViewHost::handleDataRetrieved(int totalCount) {
165165
void ClipboardHistoryViewHost::loadDetail(const ClipboardHistoryEntry &entry) {
166166
m_detailTextContent.clear();
167167
m_detailImageSource.clear();
168+
m_detailOgTitle.clear();
169+
m_detailOgDescription.clear();
170+
m_detailOgImageUrl.clear();
168171
m_hasDetailError = false;
169172
m_detailErrorTitle.clear();
170173
m_detailErrorDescription.clear();
171174

175+
m_detailOgTitle = entry.ogTitle.value_or(QString());
176+
m_detailOgDescription = entry.ogDescription.value_or(QString());
177+
m_detailOgImageUrl = entry.ogImage.value_or(QString());
172178
m_detailMimeType = entry.mimeType;
173179
m_detailSize = formatSize(entry.size);
174180
m_detailCopiedAt = QDateTime::fromSecsSinceEpoch(entry.updatedAt).toString();
@@ -268,6 +274,9 @@ void ClipboardHistoryViewHost::clearDetail() {
268274
m_hasDetailError = false;
269275
m_detailTextContent.clear();
270276
m_detailImageSource.clear();
277+
m_detailOgTitle.clear();
278+
m_detailOgDescription.clear();
279+
m_detailOgImageUrl.clear();
271280
m_detailMimeType.clear();
272281
m_detailSize.clear();
273282
m_detailCopiedAt.clear();

src/server/src/qml/clipboard-history-view-host.hpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ class ClipboardHistoryViewHost : public ViewHostBase {
2727
Q_PROPERTY(QString detailEncryptionIcon READ detailEncryptionIcon NOTIFY detailChanged)
2828
Q_PROPERTY(QString detailErrorTitle READ detailErrorTitle NOTIFY detailChanged)
2929
Q_PROPERTY(QString detailErrorDescription READ detailErrorDescription NOTIFY detailChanged)
30+
Q_PROPERTY(QString detailOgTitle READ detailOgTitle NOTIFY detailChanged)
31+
Q_PROPERTY(QString detailOgDescription READ detailOgDescription NOTIFY detailChanged)
32+
Q_PROPERTY(QString detailOgImageUrl READ detailOgImageUrl NOTIFY detailChanged)
3033

3134
public:
3235
explicit ClipboardHistoryViewHost();
@@ -60,6 +63,9 @@ class ClipboardHistoryViewHost : public ViewHostBase {
6063
QString detailEncryptionIcon() const { return m_detailEncryptionIcon; }
6164
QString detailErrorTitle() const { return m_detailErrorTitle; }
6265
QString detailErrorDescription() const { return m_detailErrorDescription; }
66+
QString detailOgTitle() const { return m_detailOgTitle; }
67+
QString detailOgDescription() const { return m_detailOgDescription; }
68+
QString detailOgImageUrl() const { return m_detailOgImageUrl; }
6369

6470
signals:
6571
void itemCountTextChanged();
@@ -96,6 +102,9 @@ class ClipboardHistoryViewHost : public ViewHostBase {
96102
QString m_detailEncryptionIcon;
97103
QString m_detailErrorTitle;
98104
QString m_detailErrorDescription;
105+
QString m_detailOgTitle;
106+
QString m_detailOgDescription;
107+
QString m_detailOgImageUrl;
99108

100109
std::unique_ptr<QTemporaryFile> m_tmpFile;
101110
};

0 commit comments

Comments
 (0)