mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
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:
parent
1e123e35cf
commit
814afdf189
@ -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()
|
||||
# }}}
|
||||
|
@ -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', {})
|
||||
|
@ -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()
|
||||
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user