diff --git a/manual/url_scheme.rst b/manual/url_scheme.rst index 269941e198..84f22f966f 100644 --- a/manual/url_scheme.rst +++ b/manual/url_scheme.rst @@ -127,6 +127,29 @@ This opens a book details window on the specified book from the specified librar current library or the selected book. +Open the notes associated with an author/tag/etc +------------------------------------------------------ + +The URL syntax is:: + + calibre://book-details/Library_Name/Field_Name/id_Item_Id + +This opens a window showing the notes of the specified item. +The easiest way to create such URLs is to show the notes you want +in calibre and click the :guilabel:`Copy URL` button to copy the URL +to the clipboard and paste it wherever you need. + +Here ``Field_Name`` is the name of the columns such as ``authors`` or ``tags``. +For user created columns, replace the leading ``#`` in the field name with +an underscore, so ``#mytags`` becomes ``_mytags``. + +In addition to specifying items by id using ``Item_Id`` you can also specify +them by name using either ``val_Item_Name`` or ``hex_Hex_Encoded_Item_Name``. +For example:: + + calibre://book-details/Library_Name/authors/val_John%20Doe + + .. _hex_encoding: Hex encoding of URL parameters diff --git a/src/calibre/gui2/dialogs/show_category_note.py b/src/calibre/gui2/dialogs/show_category_note.py index 36833a932f..a7149b0e18 100644 --- a/src/calibre/gui2/dialogs/show_category_note.py +++ b/src/calibre/gui2/dialogs/show_category_note.py @@ -3,8 +3,8 @@ import os from qt.core import ( - QByteArray, QDialog, QDialogButtonBox, QIcon, QLabel, QSize, Qt, QTextDocument, - QVBoxLayout, + QApplication, QByteArray, QDialog, QDialogButtonBox, QIcon, QLabel, QMimeData, + QSize, Qt, QTextDocument, QUrl, QVBoxLayout, ) from calibre import prepare_string_for_xml @@ -97,6 +97,11 @@ class ShowNoteDialog(Dialog): b.setToolTip(_('Search the calibre library for books by: {}').format(self.item_val)) else: b.setToolTip(_('Search the calibre library for books with: {}').format(self.item_val)) + b = self.bb.addButton(_('Copy &URL'), QDialogButtonBox.ButtonRole.ActionRole) + b.setIcon(QIcon.ic('insert-link.png')) + b.clicked.connect(self.copy_url) + b.setToolTip(_('Copy a calibre:// URL to the clipboard that can be used to link to this note from other programs')) + l.addWidget(self.bb) def sizeHint(self): @@ -105,6 +110,17 @@ class ShowNoteDialog(Dialog): def open_item_link(self, url): safe_open_url(url) + def copy_url(self): + f = self.field + if f.startswith('#'): + f = '_' + f[1:] + url = f'calibre://show-note/{self.db.server_library_id}/{f}/id_{self.item_id}' + cb = QApplication.instance().clipboard() + md = QMimeData() + md.setText(url) + md.setUrls([QUrl(url)]) + cb.setMimeData(md) + def edit(self): d = EditNoteDialog(self.field, self.item_id, self.db, self) if d.exec() == QDialog.DialogCode.Accepted: diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index 49f6c3e156..ea8ee3b3b2 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -709,6 +709,52 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ return details = self.iactions['Show Book Details'] details.show_book_info(library_id=library_id, library_path=library_path, book_id=book_id) + elif action == 'show-note': + parts = tuple(filter(None, path.split('/'))) + if len(parts) != 3: + return + library_id, field, itemx = parts + library_id = decode_library_id(library_id) + library_path = self.library_broker.path_for_library_id(library_id) + if library_path is None: + prints('Ignoring unknown library id', library_id, file=sys.stderr) + return + if field.startswith('_'): + field = '#' + field[1:] + item_id = item_val = None + if itemx.startswith('id_'): + try: + item_id = int(itemx[3:]) + except Exception: + prints('Ignoring invalid item id', itemx, file=sys.stderr) + return + elif itemx.startswith('hex_'): + try: + item_val = bytes.fromhex(itemx[4:]).decode('utf-8') + except Exception: + prints('Ignoring invalid item hexval', itemx, file=sys.stderr) + return + elif itemx.startswith('val_'): + item_val = itemx[4:] + else: + prints('Ignoring invalid item hexval', itemx, file=sys.stderr) + return + + def doit(): + nonlocal item_id, item_val + db = self.current_db.new_api + if item_id is None: + item_id = db.get_item_id(field, item_val) + if item_id is None: + prints('The item named:', item_val, 'was not found', file=sys.stderr) + return + if db.notes_for(field, item_id): + from calibre.gui2.dialogs.show_category_note import ShowNoteDialog + ShowNoteDialog(field, item_id, db, parent=self).show() + else: + prints(f'No notes available for {field}:{itemx}', file=sys.stderr) + + self.perform_url_action(library_id, library_path, doit) elif action == 'show-book': parts = tuple(filter(None, path.split('/'))) if len(parts) != 2: