From 0efd8c7b6d4e6e820acc1d6ed0560eba2a04212a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 8 Feb 2025 12:32:52 +0530 Subject: [PATCH] Content server: When clicking on author names in the book details page perform the same action as clicking it in the calibre program's book details panel --- src/calibre/srv/code.py | 29 +++++++++++++++++++++++++++-- src/calibre/srv/errors.py | 6 ++++++ src/pyj/book_list/book_details.pyj | 29 ++++++++++++++++++++++------- src/pyj/book_list/details_list.pyj | 2 +- src/pyj/book_list/library_data.pyj | 4 ++++ src/pyj/session.pyj | 1 + 6 files changed, 61 insertions(+), 10 deletions(-) diff --git a/src/calibre/srv/code.py b/src/calibre/srv/code.py index 1e33713d17..fca4331912 100644 --- a/src/calibre/srv/code.py +++ b/src/calibre/srv/code.py @@ -15,10 +15,11 @@ from calibre import as_unicode from calibre.constants import in_develop_mode from calibre.customize.ui import available_input_formats from calibre.db.view import sanitize_sort_field_name +from calibre.ebooks.metadata.book.render import resolve_default_author_link from calibre.srv.ajax import search_result -from calibre.srv.errors import BookNotFound, HTTPBadRequest, HTTPForbidden, HTTPNotFound, HTTPRedirect +from calibre.srv.errors import BookNotFound, HTTPBadRequest, HTTPForbidden, HTTPNotFound, HTTPRedirect, HTTPTempRedirect from calibre.srv.last_read import last_read_cache -from calibre.srv.metadata import book_as_json, categories_as_json, categories_settings, icon_map +from calibre.srv.metadata import book_as_json, categories_as_json, categories_settings, get_gpref, icon_map, web_search_link from calibre.srv.routes import endpoint, json from calibre.srv.utils import get_library_data, get_use_roman from calibre.utils.config import prefs, tweaks @@ -172,6 +173,7 @@ def basic_interface_data(ctx, rd): 'default_book_list_mode': rd.opts.book_list_mode, 'donate_link': localize_website_link('https://calibre-ebook.com/donate'), 'lang_code_for_user_manual': lang_code_for_user_manual(), + 'default_author_link': resolve_default_author_link(get_gpref('default_author_link')), } ans['library_map'], ans['default_library_id'] = ctx.library_info(rd) if ans['username']: @@ -419,6 +421,29 @@ def book_metadata(ctx, rd, book_id): return data +@endpoint('/web-search/{book_id}/{field}/{item_val}', postprocess=json) +def web_search(ctx, rd, book_id, field, item_val): + ''' + Redirect to a web search URL for the specified item. + Optional: ?library_id= + ''' + db, library_id = get_library_data(ctx, rd)[:2] + try: + book_id = int(book_id) + except Exception: + raise HTTPNotFound(f'Book with id {book_id!r} does not exist') + if db is None: + raise HTTPNotFound(f'Library {library_id!r} not found') + with db.safe_read_lock: + if not ctx.has_id(rd, db, book_id): + raise BookNotFound(book_id, db) + mi = db.get_metadata(book_id, get_cover=False) + url, tooltip = web_search_link(db, book_id, field, item_val) + if url: + raise HTTPTempRedirect(url) + raise HTTPNotFound(f'No web search URL for {field} {item_val}') + + @endpoint('/interface-data/tag-browser') def tag_browser(ctx, rd): ''' diff --git a/src/calibre/srv/errors.py b/src/calibre/srv/errors.py index 7c449cd7bf..ca90928337 100644 --- a/src/calibre/srv/errors.py +++ b/src/calibre/srv/errors.py @@ -32,6 +32,12 @@ class HTTPRedirect(HTTPSimpleResponse): HTTPSimpleResponse.__init__(self, http_code, http_message, close_connection, location) +class HTTPTempRedirect(HTTPSimpleResponse): + + def __init__(self, location, http_code=http_client.TEMPORARY_REDIRECT, http_message='', close_connection=False): + HTTPSimpleResponse.__init__(self, http_code, http_message, close_connection, location) + + class HTTPNotFound(HTTPSimpleResponse): def __init__(self, http_message='', close_connection=False): diff --git a/src/pyj/book_list/book_details.pyj b/src/pyj/book_list/book_details.pyj index 77b7178b26..e3888c7f79 100644 --- a/src/pyj/book_list/book_details.pyj +++ b/src/pyj/book_list/book_details.pyj @@ -12,7 +12,7 @@ from book_list.item_list import create_item, create_item_list, create_side_actio from book_list.library_data import ( all_libraries, book_after, book_metadata, cover_url, current_library_id, current_virtual_library, download_url, library_data, load_status, - set_book_metadata, download_data_file_url + set_book_metadata, download_data_file_url, web_search_url ) from book_list.router import back, home, open_book, report_a_load_failure, show_note, open_book_url_in_library, search_url_in_library from book_list.theme import ( @@ -286,18 +286,33 @@ def render_metadata(mi, table, book_id, iframe_css): # {{{ v += '' parent = table.lastChild.lastChild if is_searchable: + websearch_link = False + if field is 'authors': + websearch_link = bool(interface_data.default_author_link) and interface_data.default_author_link is not 'search-calibre' text_rep = search_text or v - parent.appendChild(E.a( - v, - title=_('Click to see books with {0}: {1}').format(name, text_rep), class_='blue-link', - href=href_for_search(is_searchable, text_rep, use_quotes=use_quotes) - )) + target = '_self' + if websearch_link and jstype(v) is not 'string': + websearch_link = False + calibre_search_url = href_for_search(is_searchable, text_rep, use_quotes=use_quotes) + calibre_search_tooltip = _('Click to see books with {0}: {1}').format(name, text_rep) + if websearch_link: + url = web_search_url(book_id, field, v) + target = '_blank' + tooltip = _('Click to browse for {} on the web').format(text_rep) + else: + url = calibre_search_url + tooltip = calibre_search_tooltip + parent.appendChild(E.a(v, target=target, title=tooltip, class_='blue-link', href=url)) if link_maps[field] and link_maps[field][text_rep]: url = link_maps[field][text_rep] if url.startswith('https://') or url.startswith('http://'): parent.appendChild(document.createTextNode(' ')) parent.appendChild(E.a( - svgicon('external-link'), title=_('Click to open') + ': ' + url, href=url, target='_new', class_='blue-link')) + svgicon('external-link'), title=_('Click to open') + ': ' + url, href=url, target='_blank', class_='blue-link')) + if websearch_link: + parent.appendChild(document.createTextNode(' ')) + parent.appendChild(E.a( + svgicon('search'), title=calibre_search_tooltip, href=calibre_search_url, class_='blue-link')) else: if v.appendChild: parent.appendChild(v) diff --git a/src/pyj/book_list/details_list.pyj b/src/pyj/book_list/details_list.pyj index 5c814d8554..6c9f4c8bc0 100644 --- a/src/pyj/book_list/details_list.pyj +++ b/src/pyj/book_list/details_list.pyj @@ -99,7 +99,7 @@ def create_item(book_id, metadata, create_image, show_book_details, href): ival = fmt_sidx(ival, use_roman=interface_data.use_roman_numerals_for_series_number) extra_data.appendChild(safe_set_inner_html(E.span(), _('{0} of {1}').format(ival, metadata.series))) right = E.div( - class_='details-list-right', + class_='details-list-right', E.div(style='display:flex; justify-content: space-between; overflow: hidden', E.div( E.b(metadata.title or _('Unknown')), E.br(), authors, diff --git a/src/pyj/book_list/library_data.pyj b/src/pyj/book_list/library_data.pyj index be18ecfa4e..d3ff737297 100644 --- a/src/pyj/book_list/library_data.pyj +++ b/src/pyj/book_list/library_data.pyj @@ -233,6 +233,10 @@ def download_data_file_url(book_id, relpath, content_disposition): return ans +def web_search_url(book_id, field, item_val): + return absolute_path(f'web-search/{book_id}/{encodeURIComponent(field)}/{encodeURIComponent(item_val)}?library_id={encodeURIComponent(current_library_id())}') + + def book_metadata(book_id): return library_data.metadata[book_id] diff --git a/src/pyj/session.pyj b/src/pyj/session.pyj index c92be56508..d731a3c986 100644 --- a/src/pyj/session.pyj +++ b/src/pyj/session.pyj @@ -313,6 +313,7 @@ default_interface_data = { 'library_map': None, 'search_the_net_urls': [], 'donate_link': 'https://calibre-ebook.com/donate', + 'default_author_link': '', 'icon_map': {}, 'icon_path': '', 'custom_list_template': None,