mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Edit book: When right clicking on HTML files in EPUB 3 books, allow marking them as the Table of Contents (NAV document). Fixes #1990507 [[Editor][Request]: Context menu entry to "Mark as NAV"](https://bugs.launchpad.net/calibre/+bug/1990507)
This commit is contained in:
parent
19cb5117f0
commit
45db1ac43c
@ -975,18 +975,26 @@ def read_raster_cover(root, prefixes, refines):
|
|||||||
return href
|
return href
|
||||||
|
|
||||||
|
|
||||||
def ensure_is_only_raster_cover(root, prefixes, refines, raster_cover_item_href):
|
def set_unique_property(property_name, root, prefixes, href):
|
||||||
for item in XPath('./opf:metadata/opf:meta[@name="cover"]')(root):
|
changed = False
|
||||||
remove_element(item, refines)
|
for item in items_with_property(root, property_name, prefixes):
|
||||||
for item in items_with_property(root, 'cover-image', prefixes):
|
prop = normalize_whitespace(item.get('properties').replace(property_name, ''))
|
||||||
prop = normalize_whitespace(item.get('properties').replace('cover-image', ''))
|
changed = True
|
||||||
if prop:
|
if prop:
|
||||||
item.set('properties', prop)
|
item.set('properties', prop)
|
||||||
else:
|
else:
|
||||||
del item.attrib['properties']
|
del item.attrib['properties']
|
||||||
for item in XPath('./opf:manifest/opf:item')(root):
|
for item in XPath('./opf:manifest/opf:item')(root):
|
||||||
if item.get('href') == raster_cover_item_href:
|
if item.get('href') == href:
|
||||||
item.set('properties', normalize_whitespace((item.get('properties') or '') + ' cover-image'))
|
changed = True
|
||||||
|
item.set('properties', normalize_whitespace((item.get('properties') or '') + f' {property_name}'))
|
||||||
|
return changed
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_is_only_raster_cover(root, prefixes, refines, raster_cover_item_href):
|
||||||
|
for item in XPath('./opf:metadata/opf:meta[@name="cover"]')(root):
|
||||||
|
remove_element(item, refines)
|
||||||
|
set_unique_property('cover-image', root, prefixes, raster_cover_item_href)
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
@ -280,6 +280,15 @@ def find_existing_nav_toc(container):
|
|||||||
return name
|
return name
|
||||||
|
|
||||||
|
|
||||||
|
def mark_as_nav(container, name):
|
||||||
|
from calibre.ebooks.metadata.opf3 import read_prefixes, set_unique_property
|
||||||
|
if container.opf_version_parsed.major > 2:
|
||||||
|
prefixes = read_prefixes(container.opf)
|
||||||
|
href = container.href_to_name(name, container.opf_name)
|
||||||
|
if set_unique_property('nav', container.opf, prefixes, href):
|
||||||
|
container.dirty(container.opf_name)
|
||||||
|
|
||||||
|
|
||||||
def get_x_toc(container, find_toc, parse_toc, verify_destinations=True):
|
def get_x_toc(container, find_toc, parse_toc, verify_destinations=True):
|
||||||
def empty_toc():
|
def empty_toc():
|
||||||
ans = TOC()
|
ans = TOC()
|
||||||
|
@ -8,7 +8,6 @@ import shutil
|
|||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
from functools import partial, wraps
|
from functools import partial, wraps
|
||||||
|
|
||||||
from qt.core import (
|
from qt.core import (
|
||||||
QApplication, QCheckBox, QDialog, QDialogButtonBox, QGridLayout, QIcon,
|
QApplication, QCheckBox, QDialog, QDialogButtonBox, QGridLayout, QIcon,
|
||||||
QInputDialog, QLabel, QMimeData, QObject, QSize, Qt, QTimer, QUrl, QVBoxLayout,
|
QInputDialog, QLabel, QMimeData, QObject, QSize, Qt, QTimer, QUrl, QVBoxLayout,
|
||||||
@ -31,7 +30,9 @@ from calibre.ebooks.oeb.polish.replace import (
|
|||||||
get_recommended_folders, rationalize_folders, rename_files, replace_file
|
get_recommended_folders, rationalize_folders, rename_files, replace_file
|
||||||
)
|
)
|
||||||
from calibre.ebooks.oeb.polish.split import AbortError, merge, multisplit, split
|
from calibre.ebooks.oeb.polish.split import AbortError, merge, multisplit, split
|
||||||
from calibre.ebooks.oeb.polish.toc import create_inline_toc, remove_names_from_toc
|
from calibre.ebooks.oeb.polish.toc import (
|
||||||
|
create_inline_toc, mark_as_nav, remove_names_from_toc
|
||||||
|
)
|
||||||
from calibre.ebooks.oeb.polish.utils import (
|
from calibre.ebooks.oeb.polish.utils import (
|
||||||
link_stylesheets, setup_css_parser_serialization as scs
|
link_stylesheets, setup_css_parser_serialization as scs
|
||||||
)
|
)
|
||||||
@ -60,12 +61,12 @@ from calibre.gui2.tweak_book.spell import (
|
|||||||
from calibre.gui2.tweak_book.toc import TOCEditor
|
from calibre.gui2.tweak_book.toc import TOCEditor
|
||||||
from calibre.gui2.tweak_book.undo import GlobalUndoHistory
|
from calibre.gui2.tweak_book.undo import GlobalUndoHistory
|
||||||
from calibre.gui2.tweak_book.widgets import (
|
from calibre.gui2.tweak_book.widgets import (
|
||||||
AddCover, FilterCSS, ImportForeign, InsertLink, InsertSemantics,
|
AddCover, FilterCSS, ImportForeign, InsertLink, InsertSemantics, InsertTag,
|
||||||
InsertTag, MultiSplit, QuickOpen, RationalizeFolders
|
MultiSplit, QuickOpen, RationalizeFolders
|
||||||
)
|
)
|
||||||
|
from calibre.gui2.widgets import BusyCursor
|
||||||
from calibre.ptempfile import PersistentTemporaryDirectory, TemporaryDirectory
|
from calibre.ptempfile import PersistentTemporaryDirectory, TemporaryDirectory
|
||||||
from calibre.utils.config import JSONConfig
|
from calibre.utils.config import JSONConfig
|
||||||
from calibre.gui2.widgets import BusyCursor
|
|
||||||
from calibre.utils.icu import numeric_sort_key
|
from calibre.utils.icu import numeric_sort_key
|
||||||
from calibre.utils.imghdr import identify
|
from calibre.utils.imghdr import identify
|
||||||
from calibre.utils.tdir_in_cache import tdir_in_cache
|
from calibre.utils.tdir_in_cache import tdir_in_cache
|
||||||
@ -213,7 +214,9 @@ class Boss(QObject):
|
|||||||
for ed in itervalues(editors):
|
for ed in itervalues(editors):
|
||||||
ed.apply_settings(dictionaries_changed=p.dictionaries_changed)
|
ed.apply_settings(dictionaries_changed=p.dictionaries_changed)
|
||||||
if orig_spell != tprefs['inline_spell_check']:
|
if orig_spell != tprefs['inline_spell_check']:
|
||||||
from calibre.gui2.tweak_book.editor.syntax.html import refresh_spell_check_status
|
from calibre.gui2.tweak_book.editor.syntax.html import (
|
||||||
|
refresh_spell_check_status
|
||||||
|
)
|
||||||
refresh_spell_check_status()
|
refresh_spell_check_status()
|
||||||
for ed in itervalues(editors):
|
for ed in itervalues(editors):
|
||||||
try:
|
try:
|
||||||
@ -230,6 +233,8 @@ class Boss(QObject):
|
|||||||
action, move_to_start = action.partition(':')[0::2]
|
action, move_to_start = action.partition(':')[0::2]
|
||||||
move_to_start = move_to_start == 'True'
|
move_to_start = move_to_start == 'True'
|
||||||
mark_as_titlepage(current_container(), name, move_to_start=move_to_start)
|
mark_as_titlepage(current_container(), name, move_to_start=move_to_start)
|
||||||
|
elif action == 'nav':
|
||||||
|
mark_as_nav(current_container(), name)
|
||||||
|
|
||||||
if c.opf_name in editors:
|
if c.opf_name in editors:
|
||||||
editors[c.opf_name].replace_data(c.raw_data(c.opf_name))
|
editors[c.opf_name].replace_data(c.raw_data(c.opf_name))
|
||||||
@ -490,7 +495,9 @@ class Boss(QObject):
|
|||||||
return
|
return
|
||||||
added_name = self.do_add_file(d.file_name, d.file_data, using_template=d.using_template, edit_file=True)
|
added_name = self.do_add_file(d.file_name, d.file_data, using_template=d.using_template, edit_file=True)
|
||||||
if d.file_name.rpartition('.')[2].lower() in ('ttf', 'otf', 'woff'):
|
if d.file_name.rpartition('.')[2].lower() in ('ttf', 'otf', 'woff'):
|
||||||
from calibre.gui2.tweak_book.manage_fonts import show_font_face_rule_for_font_file
|
from calibre.gui2.tweak_book.manage_fonts import (
|
||||||
|
show_font_face_rule_for_font_file
|
||||||
|
)
|
||||||
show_font_face_rule_for_font_file(d.file_data, added_name, self.gui)
|
show_font_face_rule_for_font_file(d.file_data, added_name, self.gui)
|
||||||
|
|
||||||
def do_add_file(self, file_name, data, using_template=False, edit_file=False):
|
def do_add_file(self, file_name, data, using_template=False, edit_file=False):
|
||||||
@ -553,7 +560,9 @@ class Boss(QObject):
|
|||||||
self.set_modified()
|
self.set_modified()
|
||||||
completion_worker().clear_caches('names')
|
completion_worker().clear_caches('names')
|
||||||
if added_fonts:
|
if added_fonts:
|
||||||
from calibre.gui2.tweak_book.manage_fonts import show_font_face_rule_for_font_files
|
from calibre.gui2.tweak_book.manage_fonts import (
|
||||||
|
show_font_face_rule_for_font_files
|
||||||
|
)
|
||||||
show_font_face_rule_for_font_files(c, added_fonts, self.gui)
|
show_font_face_rule_for_font_files(c, added_fonts, self.gui)
|
||||||
|
|
||||||
def add_cover(self):
|
def add_cover(self):
|
||||||
@ -633,8 +642,8 @@ class Boss(QObject):
|
|||||||
global last_used_html_transform_rules
|
global last_used_html_transform_rules
|
||||||
if not self.ensure_book(_('You must first open a book in order to transform styles.')):
|
if not self.ensure_book(_('You must first open a book in order to transform styles.')):
|
||||||
return
|
return
|
||||||
from calibre.gui2.html_transform_rules import RulesDialog
|
|
||||||
from calibre.ebooks.html_transform_rules import transform_container
|
from calibre.ebooks.html_transform_rules import transform_container
|
||||||
|
from calibre.gui2.html_transform_rules import RulesDialog
|
||||||
d = RulesDialog(self.gui)
|
d = RulesDialog(self.gui)
|
||||||
d.rules = last_used_html_transform_rules
|
d.rules = last_used_html_transform_rules
|
||||||
d.transform_scope = tprefs['html_transform_scope']
|
d.transform_scope = tprefs['html_transform_scope']
|
||||||
@ -677,8 +686,8 @@ class Boss(QObject):
|
|||||||
global last_used_transform_rules
|
global last_used_transform_rules
|
||||||
if not self.ensure_book(_('You must first open a book in order to transform styles.')):
|
if not self.ensure_book(_('You must first open a book in order to transform styles.')):
|
||||||
return
|
return
|
||||||
from calibre.gui2.css_transform_rules import RulesDialog
|
|
||||||
from calibre.ebooks.css_transform_rules import transform_container
|
from calibre.ebooks.css_transform_rules import transform_container
|
||||||
|
from calibre.gui2.css_transform_rules import RulesDialog
|
||||||
d = RulesDialog(self.gui)
|
d = RulesDialog(self.gui)
|
||||||
d.rules = last_used_transform_rules
|
d.rules = last_used_transform_rules
|
||||||
ret = d.exec()
|
ret = d.exec()
|
||||||
@ -1203,7 +1212,9 @@ class Boss(QObject):
|
|||||||
'No file with the name %s was found in the book') % target, show=True)
|
'No file with the name %s was found in the book') % target, show=True)
|
||||||
|
|
||||||
def editor_class_clicked(self, class_data):
|
def editor_class_clicked(self, class_data):
|
||||||
from calibre.gui2.tweak_book.jump_to_class import find_first_matching_rule, NoMatchingTagFound, NoMatchingRuleFound
|
from calibre.gui2.tweak_book.jump_to_class import (
|
||||||
|
NoMatchingRuleFound, NoMatchingTagFound, find_first_matching_rule
|
||||||
|
)
|
||||||
ed = self.gui.central.current_editor
|
ed = self.gui.central.current_editor
|
||||||
name = editor_name(ed)
|
name = editor_name(ed)
|
||||||
try:
|
try:
|
||||||
@ -1594,7 +1605,9 @@ class Boss(QObject):
|
|||||||
def compress_images(self):
|
def compress_images(self):
|
||||||
if not self.ensure_book(_('You must first open a book in order to compress images.')):
|
if not self.ensure_book(_('You must first open a book in order to compress images.')):
|
||||||
return
|
return
|
||||||
from calibre.gui2.tweak_book.polish import show_report, CompressImages, CompressImagesProgress
|
from calibre.gui2.tweak_book.polish import (
|
||||||
|
CompressImages, CompressImagesProgress, show_report
|
||||||
|
)
|
||||||
d = CompressImages(self.gui)
|
d = CompressImages(self.gui)
|
||||||
if d.exec() == QDialog.DialogCode.Accepted:
|
if d.exec() == QDialog.DialogCode.Accepted:
|
||||||
with BusyCursor():
|
with BusyCursor():
|
||||||
@ -1650,8 +1663,8 @@ class Boss(QObject):
|
|||||||
editor = self.edit_file(name, 'html')
|
editor = self.edit_file(name, 'html')
|
||||||
if not editor or not editor.has_line_numbers:
|
if not editor or not editor.has_line_numbers:
|
||||||
return False
|
return False
|
||||||
from calibre.ebooks.oeb.polish.parsing import parse
|
|
||||||
from calibre.ebooks.epub.cfi.parse import decode_cfi
|
from calibre.ebooks.epub.cfi.parse import decode_cfi
|
||||||
|
from calibre.ebooks.oeb.polish.parsing import parse
|
||||||
root = parse(
|
root = parse(
|
||||||
editor.get_raw_data(), decoder=lambda x: x.decode('utf-8'),
|
editor.get_raw_data(), decoder=lambda x: x.decode('utf-8'),
|
||||||
line_numbers=True, linenumber_attribute='data-lnum')
|
line_numbers=True, linenumber_attribute='data-lnum')
|
||||||
|
@ -705,6 +705,8 @@ class FileList(QTreeWidget, OpenWithHandler):
|
|||||||
m.addAction(QIcon.ic('default_cover.png'), _('Mark %s as cover image') % n, partial(self.mark_as_cover, cn))
|
m.addAction(QIcon.ic('default_cover.png'), _('Mark %s as cover image') % n, partial(self.mark_as_cover, cn))
|
||||||
elif current_container().SUPPORTS_TITLEPAGES and mt in OEB_DOCS and cat == 'text':
|
elif current_container().SUPPORTS_TITLEPAGES and mt in OEB_DOCS and cat == 'text':
|
||||||
m.addAction(QIcon.ic('default_cover.png'), _('Mark %s as cover page') % n, partial(self.mark_as_titlepage, cn))
|
m.addAction(QIcon.ic('default_cover.png'), _('Mark %s as cover page') % n, partial(self.mark_as_titlepage, cn))
|
||||||
|
if mt in OEB_DOCS and cat in ('text', 'misc') and current_container().opf_version_parsed.major > 2:
|
||||||
|
m.addAction(QIcon.ic('toc.png'), _('Mark %s as Table of Contents') % n, partial(self.mark_as_nav, cn))
|
||||||
m.addSeparator()
|
m.addSeparator()
|
||||||
|
|
||||||
if num > 0:
|
if num > 0:
|
||||||
@ -791,6 +793,9 @@ class FileList(QTreeWidget, OpenWithHandler):
|
|||||||
)
|
)
|
||||||
self.mark_requested.emit(name, 'titlepage:%r' % move_to_start)
|
self.mark_requested.emit(name, 'titlepage:%r' % move_to_start)
|
||||||
|
|
||||||
|
def mark_as_nav(self, name):
|
||||||
|
self.mark_requested.emit(name, 'nav')
|
||||||
|
|
||||||
def keyPressEvent(self, ev):
|
def keyPressEvent(self, ev):
|
||||||
if ev.key() in (Qt.Key.Key_Delete, Qt.Key.Key_Backspace):
|
if ev.key() in (Qt.Key.Key_Delete, Qt.Key.Key_Backspace):
|
||||||
ev.accept()
|
ev.accept()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user