diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py
index b707ec8569..ef6ca9284c 100644
--- a/src/calibre/db/cache.py
+++ b/src/calibre/db/cache.py
@@ -697,6 +697,23 @@ class Cache:
return self.backend.notes.allowed_fields
return field in self.backend.notes.allowed_fields
+ @read_api
+ def items_with_notes_in_book(self, book_id: int) -> dict[str, dict[int, str]]:
+ ' Return a dict of field to items that have associated notes for that field for the specified book '
+ ans = {}
+ for k in self.backend.notes.allowed_fields:
+ try:
+ field = self.fields[k]
+ except KeyError:
+ continue
+ v = {}
+ for item_id in field.ids_for_book(book_id):
+ if self.backend.notes_for(k, item_id):
+ v[item_id] = field.table.id_map[item_id]
+ if v:
+ ans[k] = v
+ return ans
+
@write_api
def set_notes_for(self, field, item_id, doc: str, searchable_text: str = copy_marked_up_text, resource_hashes=(), remove_unused_resources=False) -> int:
'''
diff --git a/src/calibre/ebooks/metadata/book/render.py b/src/calibre/ebooks/metadata/book/render.py
index 8f411350d3..8d1bd7d426 100644
--- a/src/calibre/ebooks/metadata/book/render.py
+++ b/src/calibre/ebooks/metadata/book/render.py
@@ -134,7 +134,7 @@ def mi_to_html(
item_id = None if item_id_if_has_note is None else item_id_if_has_note(field, field_value)
if item_id is not None:
note = ' {2}'.format(
- _('Click to open note'), notes_action(field=field, value=field_value, item_id=item_id), note_markup)
+ _('Show notes for: {}').format(field_value), notes_action(field=field, value=field_value, item_id=item_id), note_markup)
return link + note
return ''
diff --git a/src/calibre/srv/code.py b/src/calibre/srv/code.py
index 9fcf8a24e7..9d48d91ca6 100644
--- a/src/calibre/srv/code.py
+++ b/src/calibre/srv/code.py
@@ -234,6 +234,7 @@ def get_library_init_data(ctx, rd, db, num, sorts, orders, vl):
ans['book_display_fields'] = get_field_list(db)
ans['fts_enabled'] = db.is_fts_enabled()
ans['book_details_vertical_categories'] = db._pref('book_details_vertical_categories', ())
+ ans['fields_that_support_notes'] = tuple(db._field_supports_notes())
mdata = ans['metadata'] = {}
try:
extra_books = {
diff --git a/src/calibre/srv/metadata.py b/src/calibre/srv/metadata.py
index eaef505f1e..d600b909e5 100644
--- a/src/calibre/srv/metadata.py
+++ b/src/calibre/srv/metadata.py
@@ -90,6 +90,9 @@ def book_as_json(db, book_id):
link_maps = db.get_all_link_maps_for_book(book_id)
if link_maps:
ans['link_maps'] = link_maps
+ x = db.items_with_notes_in_book(book_id)
+ if x:
+ ans['items_with_notes'] = {field: {v: k for k, v in items.items()} for field, items in x.items()}
return ans
diff --git a/src/pyj/book_list/book_details.pyj b/src/pyj/book_list/book_details.pyj
index 918bbe2168..4274001ac4 100644
--- a/src/pyj/book_list/book_details.pyj
+++ b/src/pyj/book_list/book_details.pyj
@@ -14,7 +14,7 @@ from book_list.library_data import (
current_virtual_library, download_url, library_data, load_status,
set_book_metadata
)
-from book_list.router import back, home, open_book, report_a_load_failure
+from book_list.router import back, home, open_book, report_a_load_failure, show_note_url
from book_list.theme import (
color_scheme, get_color, get_color_as_rgba, get_font_size
)
@@ -192,6 +192,13 @@ def render_metadata(mi, table, book_id, iframe_css): # {{{
comments = v'[]'
link_maps = mi.link_maps or v'{}'
+ def add_note_link(field, name, val, parent):
+ if mi.items_with_notes[field] and mi.items_with_notes[field][val]:
+ parent.appendChild(document.createTextNode(' '))
+ parent.appendChild(E.a(
+ svgicon('pencil'), title=_('Show notes for: {}').format(val), href=show_note_url(
+ book_id, field, val, close_action='close_window'), target='_new', class_='blue-link'))
+
def add_row(field, name, val, is_searchable=False, is_html=False, join=None, search_text=None, use_quotes=True):
if val is undefined or val is None:
return
@@ -219,6 +226,8 @@ def render_metadata(mi, table, book_id, iframe_css): # {{{
parent.appendChild(v)
else:
parent.appendChild(document.createTextNode(v))
+ if jstype(v) is 'string' and not is_html:
+ add_note_link(field, name, v, parent)
table.appendChild(E.tr(E.td(name), E.td()))
if is_html and /[<>]/.test(val + ''):
@@ -337,6 +346,7 @@ def render_metadata(mi, table, book_id, iframe_css): # {{{
else:
print("WARNING: Translation of series template is incorrect as it does not have an tag")
table.lastChild.lastChild.appendChild(s)
+ add_note_link(field, name, val, table.lastChild.lastChild)
def process_field(field, fm):
name = fm.name or field
diff --git a/src/pyj/book_list/library_data.pyj b/src/pyj/book_list/library_data.pyj
index 0da3d22ce4..c955d90745 100644
--- a/src/pyj/book_list/library_data.pyj
+++ b/src/pyj/book_list/library_data.pyj
@@ -83,7 +83,7 @@ def update_library_data(data):
if library_data.for_library is not current_library_id():
library_data.field_names = {}
library_data.for_library = current_library_id()
- for key in 'search_result sortable_fields field_metadata metadata virtual_libraries book_display_fields bools_are_tristate book_details_vertical_categories fts_enabled'.split(' '):
+ for key in 'search_result sortable_fields field_metadata metadata virtual_libraries book_display_fields bools_are_tristate book_details_vertical_categories fts_enabled fields_that_support_notes'.split(' '):
library_data[key] = data[key]
sr = library_data.search_result
if sr:
diff --git a/src/pyj/book_list/router.pyj b/src/pyj/book_list/router.pyj
index fe272d0f1a..d23015edea 100644
--- a/src/pyj/book_list/router.pyj
+++ b/src/pyj/book_list/router.pyj
@@ -87,6 +87,15 @@ def open_book_url(book_id, fmt, extra_query):
return ans + encode_query(q, '#')
+def show_note_url(book_id, field, item_value, close_action='back'):
+ lid = current_library_id()
+ ans = absolute_path('')
+ q = {'book_id':book_id, 'field': field, 'item':item_value, 'panel': 'show_note', 'close_action': close_action}
+ if lid:
+ q.library_id = lid
+ return ans + encode_query(q, '#')
+
+
def push_state(query, replace=False, mode='book_list', call_handler=True):
query = {k:query[k] for k in query if query[k]}
if mode is not 'book_list':