E-book viewer: When exporting highlights, add a new Markdown export type, where the date of the highlight becomes a link to open the book in calibre showing the highlight

This commit is contained in:
Kovid Goyal 2020-12-24 13:08:13 +05:30
parent 1e123e35cf
commit 814afdf189
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
5 changed files with 73 additions and 18 deletions

View File

@ -7,11 +7,13 @@ import json
import os
from functools import partial
from PyQt5.Qt import (
QApplication, QCheckBox, QComboBox, QCursor, QDateTime, QFont, QFormLayout, QDialog,
QHBoxLayout, QIcon, QKeySequence, QLabel, QMenu, QPalette, QPlainTextEdit, QSize,
QSplitter, Qt, QTextBrowser, QTimer, QToolButton, QTreeWidget, QTreeWidgetItem, QFrame,
QVBoxLayout, QWidget, pyqtSignal, QAbstractItemView, QDialogButtonBox
QAbstractItemView, QApplication, QCheckBox, QComboBox, QCursor, QDateTime,
QDialog, QDialogButtonBox, QFont, QFormLayout, QFrame, QHBoxLayout, QIcon,
QKeySequence, QLabel, QMenu, QPalette, QPlainTextEdit, QSize, QSplitter, Qt,
QTextBrowser, QTimer, QToolButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout,
QWidget, pyqtSignal
)
from urllib.parse import quote
from calibre import prepare_string_for_xml
from calibre.ebooks.metadata import authors_to_string, fmt_sidx
@ -22,25 +24,40 @@ from calibre.gui2.widgets2 import Dialog
# rendering {{{
def render_highlight_as_text(hl, lines):
def render_highlight_as_text(hl, lines, as_markdown=False, link_prefix=None):
lines.append(hl['highlighted_text'])
date = QDateTime.fromString(hl['timestamp'], Qt.DateFormat.ISODate).toLocalTime().toString(Qt.DateFormat.SystemLocaleShortDate)
if as_markdown and link_prefix:
cfi = hl['start_cfi']
spine_index = (1 + hl['spine_index']) * 2
link = (link_prefix + quote(f'epubcfi(/{spine_index}{cfi})')).replace(')', '%29')
date = f'[{date}]({link})'
lines.append(date)
notes = hl.get('notes')
if notes:
lines.append('')
lines.append(notes)
lines.append('')
lines.append('───')
if as_markdown:
lines.append('-' * 20)
else:
lines.append('───')
lines.append('')
def render_bookmark_as_text(b, lines):
def render_bookmark_as_text(b, lines, as_markdown=False, link_prefix=None):
lines.append(b['title'])
date = QDateTime.fromString(b['timestamp'], Qt.DateFormat.ISODate).toLocalTime().toString(Qt.DateFormat.SystemLocaleShortDate)
if as_markdown and link_prefix and b['pos_type'] == 'epubcfi':
link = (link_prefix + quote(b['pos'])).replace(')', '%29')
date = f'[{date}]({link})'
lines.append(date)
lines.append('')
lines.append('───')
if as_markdown:
lines.append('-' * 20)
else:
lines.append('───')
lines.append('')
@ -115,6 +132,7 @@ class Export(Dialog): # {{{
self.l = l = QFormLayout(self)
self.export_format = ef = QComboBox(self)
ef.addItem(_('Plain text'), 'txt')
ef.addItem(_('Markdown'), 'md')
ef.addItem(*self.file_type_data())
idx = ef.findData(self.prefs[self.pref_name])
if idx > -1:
@ -151,7 +169,8 @@ class Export(Dialog): # {{{
self.accept()
def exported_data(self):
if self.export_format.currentData() == 'calibre_annotation_collection':
fmt = self.export_format.currentData()
if fmt == 'calibre_annotation_collection':
return json.dumps({
'version': 1,
'type': 'calibre_annotation_collection',
@ -160,6 +179,10 @@ class Export(Dialog): # {{{
lines = []
db = current_db()
bid_groups = {}
as_markdown = fmt == 'md'
library_id = getattr(db, 'server_library_id', None)
if library_id:
library_id = '_hex_-' + library_id.encode('utf-8').hex()
for a in self.annotations:
bid_groups.setdefault(a['book_id'], []).append(a)
for book_id, group in bid_groups.items():
@ -167,10 +190,14 @@ class Export(Dialog): # {{{
lines.append('')
for a in group:
atype = a['type']
if library_id:
link_prefix = f'calibre://show-book/{library_id}/{book_id}/{a["format"]}?open_at='
else:
link_prefix = None
if atype == 'highlight':
render_highlight_as_text(a, lines)
render_highlight_as_text(a, lines, as_markdown=as_markdown, link_prefix=link_prefix)
elif atype == 'bookmark':
render_bookmark_as_text(a, lines)
render_bookmark_as_text(a, lines, as_markdown=as_markdown, link_prefix=link_prefix)
lines.append('')
return '\n'.join(lines).strip()
# }}}

View File

@ -3,3 +3,7 @@
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
def get_current_book_data(set_val=False):
if set_val is not False:
setattr(get_current_book_data, 'ans', set_val)
return getattr(get_current_book_data, 'ans', {})

View File

@ -23,6 +23,7 @@ from calibre.gui2.dialogs.confirm_delete import confirm
from calibre.gui2.library.annotations import (
Details, Export as ExportBase, render_highlight_as_text, render_notes
)
from calibre.gui2.viewer import get_current_book_data
from calibre.gui2.viewer.config import vprefs
from calibre.gui2.viewer.search import SearchInput
from calibre.gui2.viewer.shortcuts import get_shortcut_for, index_to_key_sequence
@ -130,15 +131,31 @@ class Export(ExportBase):
return _('highlights')
def exported_data(self):
if self.export_format.currentData() == 'calibre_highlights':
cbd = get_current_book_data()
link_prefix = library_id = None
if 'calibre_library_id' in cbd:
library_id = cbd['calibre_library_id']
book_id = cbd['calibre_book_id']
book_fmt = cbd['calibre_book_fmt']
elif cbd.get('book_library_details'):
bld = cbd['book_library_details']
book_id = bld['book_id']
book_fmt = bld['fmt'].upper()
library_id = bld['library_id']
if library_id:
library_id = '_hex_-' + library_id.encode('utf-8').hex()
link_prefix = f'calibre://show-book/{library_id}/{book_id}/{book_fmt}?open_at='
fmt = self.export_format.currentData()
if fmt == 'calibre_highlights':
return json.dumps({
'version': 1,
'type': 'calibre_highlights',
'highlights': self.annotations,
}, ensure_ascii=False, sort_keys=True, indent=2)
lines = []
as_markdown = fmt == 'md'
for hl in self.annotations:
render_highlight_as_text(hl, lines)
render_highlight_as_text(hl, lines, as_markdown=as_markdown, link_prefix=link_prefix)
return '\n'.join(lines).strip()

View File

@ -7,6 +7,7 @@ import re
def get_book_library_details(absolute_path_to_ebook):
from calibre.srv.library_broker import correct_case_of_last_path_component, library_id_from_path
absolute_path_to_ebook = os.path.abspath(os.path.expanduser(absolute_path_to_ebook))
base = os.path.dirname(absolute_path_to_ebook)
m = re.search(r' \((\d+)\)$', os.path.basename(base))
@ -14,11 +15,13 @@ def get_book_library_details(absolute_path_to_ebook):
return
book_id = int(m.group(1))
library_dir = os.path.dirname(os.path.dirname(base))
corrected_path = correct_case_of_last_path_component(library_dir)
library_id = library_id_from_path(corrected_path)
dbpath = os.path.join(library_dir, 'metadata.db')
dbpath = os.environ.get('CALIBRE_OVERRIDE_DATABASE_PATH') or dbpath
if not os.path.exists(dbpath):
return
return {'dbpath': dbpath, 'book_id': book_id, 'fmt': absolute_path_to_ebook.rpartition('.')[-1].upper()}
return {'dbpath': dbpath, 'book_id': book_id, 'fmt': absolute_path_to_ebook.rpartition('.')[-1].upper(), 'library_id': library_id}
def database_has_annotations_support(cursor):

View File

@ -9,12 +9,12 @@ import re
import sys
from collections import defaultdict, namedtuple
from hashlib import sha256
from threading import Thread
from PyQt5.Qt import (
QApplication, QCursor, QDockWidget, QEvent, QMenu, QMimeData, QModelIndex,
QPixmap, Qt, QTimer, QToolBar, QUrl, QVBoxLayout, QWidget, pyqtSignal, QMainWindow
QApplication, QCursor, QDockWidget, QEvent, QMainWindow, QMenu, QMimeData,
QModelIndex, QPixmap, Qt, QTimer, QToolBar, QUrl, QVBoxLayout, QWidget,
pyqtSignal
)
from threading import Thread
from calibre import prints
from calibre.constants import DEBUG
@ -24,6 +24,7 @@ from calibre.gui2 import choose_files, error_dialog
from calibre.gui2.dialogs.drm_error import DRMErrorMessage
from calibre.gui2.image_popup import ImagePopup
from calibre.gui2.main_window import MainWindow
from calibre.gui2.viewer import get_current_book_data
from calibre.gui2.viewer.annotations import (
AnnotationsSaveWorker, annotations_dir, parse_annotations
)
@ -109,6 +110,7 @@ class EbookViewer(MainWindow):
except EnvironmentError:
pass
self.current_book_data = {}
get_current_book_data(self.current_book_data)
self.book_prepared.connect(self.load_finished, type=Qt.ConnectionType.QueuedConnection)
self.dock_defs = dock_defs()
@ -457,6 +459,7 @@ class EbookViewer(MainWindow):
self.loading_overlay(_('Loading book, please wait'))
self.save_annotations()
self.current_book_data = {}
get_current_book_data(self.current_book_data)
self.search_widget.clear_searches()
t = Thread(name='LoadBook', target=self._load_ebook_worker, args=(pathtoebook, open_at, reload_book or self.force_reload))
t.daemon = True
@ -513,6 +516,7 @@ class EbookViewer(MainWindow):
self.load_ebook(data['pathtoebook'], open_at=data['open_at'], reload_book=True)
return
self.current_book_data = data
get_current_book_data(self.current_book_data)
self.current_book_data['annotations_map'] = defaultdict(list)
self.current_book_data['annotations_path_key'] = path_key(data['pathtoebook']) + '.json'
self.load_book_data(cbd)