mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Edit book: When right clicking on a class in a HTML file, add an option to rename the class throughout the book
This commit is contained in:
parent
1d6694b04f
commit
4ad268224f
@ -824,6 +824,10 @@ You can also hold down the :kbd:`Ctrl` key and click on any filename inside a li
|
||||
to open that file in the editor automatically. Similarly, :kbd:`Ctrl` clicking
|
||||
a class name will take you to the first style rule that matches the tag and class.
|
||||
|
||||
Right clicking a class name in an HTML file will allow you to rename the class,
|
||||
changing all occurrences of the class throughout the book and all its
|
||||
stylesheets.
|
||||
|
||||
.. _editor_auto_complete:
|
||||
|
||||
Auto-complete
|
||||
|
@ -5,6 +5,7 @@
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
import re
|
||||
from collections import defaultdict
|
||||
from functools import partial
|
||||
from operator import itemgetter
|
||||
@ -463,3 +464,55 @@ def add_stylesheet_links(container, name, text):
|
||||
head.append(link)
|
||||
pretty_xml_tree(head)
|
||||
return serialize(root, 'text/html')
|
||||
|
||||
|
||||
def rename_class_in_rule_list(css_rules, old_name, new_name):
|
||||
pat = re.compile(rf'\b{re.escape(old_name)}\b')
|
||||
changed = False
|
||||
for rule in css_rules:
|
||||
if rule.type == rule.STYLE_RULE:
|
||||
old = rule.selectorText
|
||||
q = pat.sub(new_name, old)
|
||||
if q != old:
|
||||
changed = True
|
||||
rule.selectorText = q
|
||||
elif hasattr(rule, 'cssRules'):
|
||||
if rename_class_in_rule_list(rule.cssRules, old_name, new_name):
|
||||
changed = True
|
||||
return changed
|
||||
|
||||
|
||||
def rename_class_in_doc(container, root, old_name, new_name):
|
||||
changed = False
|
||||
pat = re.compile(rf'\b{re.escape(old_name)}\b')
|
||||
for elem in root.xpath('//*[@class]'):
|
||||
old = elem.get('class')
|
||||
if old:
|
||||
new = pat.sub(new_name, old)
|
||||
if new != old:
|
||||
changed = True
|
||||
elem.set('class', new)
|
||||
for style in root.xpath('//*[local-name()="style"]'):
|
||||
if style.get('type', 'text/css') == 'text/css' and style.text:
|
||||
sheet = container.parse_css(style.text)
|
||||
if rename_class_in_rule_list(sheet.cssRules, old_name, new_name):
|
||||
changed = True
|
||||
style.text = force_unicode(sheet.cssText, 'utf-8')
|
||||
return changed
|
||||
|
||||
|
||||
def rename_class(container, old_name, new_name):
|
||||
changed = False
|
||||
if not old_name or old_name == new_name:
|
||||
return changed
|
||||
for sheet_name in container.manifest_items_of_type(lambda mt: mt in OEB_STYLES):
|
||||
sheet = container.parsed(sheet_name)
|
||||
if rename_class_in_rule_list(sheet.cssRules, old_name, new_name):
|
||||
container.dirty(sheet_name)
|
||||
changed = True
|
||||
for doc_name in container.manifest_items_of_type(lambda mt: mt in OEB_DOCS):
|
||||
doc = container.parsed(doc_name)
|
||||
if rename_class_in_doc(container, doc, old_name, new_name):
|
||||
container.dirty(doc_name)
|
||||
changed = True
|
||||
return changed
|
||||
|
@ -25,7 +25,7 @@ from calibre.ebooks.oeb.polish.container import (
|
||||
from calibre.ebooks.oeb.polish.cover import (
|
||||
mark_as_cover, mark_as_titlepage, set_cover
|
||||
)
|
||||
from calibre.ebooks.oeb.polish.css import filter_css
|
||||
from calibre.ebooks.oeb.polish.css import filter_css, rename_class
|
||||
from calibre.ebooks.oeb.polish.main import SUPPORTED, tweak_polish
|
||||
from calibre.ebooks.oeb.polish.pretty import fix_all_html, pretty_all
|
||||
from calibre.ebooks.oeb.polish.replace import (
|
||||
@ -956,6 +956,22 @@ class Boss(QObject):
|
||||
else:
|
||||
ed.action_triggered(action)
|
||||
|
||||
def rename_class(self, class_name):
|
||||
self.commit_all_editors_to_container()
|
||||
text, ok = QInputDialog.getText(self.gui, _('New class name'), _(
|
||||
'Rename the class {} to?').format(class_name))
|
||||
if ok:
|
||||
self.add_savepoint(_('Before: Rename {}').format(class_name))
|
||||
with BusyCursor():
|
||||
changed = rename_class(current_container(), class_name, text.strip())
|
||||
if changed:
|
||||
self.apply_container_update_to_gui()
|
||||
self.show_current_diff()
|
||||
else:
|
||||
self.rewind_savepoint()
|
||||
return info_dialog(self.gui, _('No matches'), _(
|
||||
'No class {} found to change').format(class_name), show=True)
|
||||
|
||||
def set_semantics(self):
|
||||
self.commit_all_editors_to_container()
|
||||
c = current_container()
|
||||
@ -1609,6 +1625,8 @@ class Boss(QObject):
|
||||
editor.link_clicked.connect(self.editor_link_clicked)
|
||||
if hasattr(editor, 'class_clicked'):
|
||||
editor.class_clicked.connect(self.editor_class_clicked)
|
||||
if hasattr(editor, 'rename_class'):
|
||||
editor.rename_class.connect(self.rename_class)
|
||||
if getattr(editor, 'syntax', None) == 'html':
|
||||
editor.smart_highlighting_updated.connect(self.gui.live_css.sync_to_editor)
|
||||
if hasattr(editor, 'set_request_completion'):
|
||||
|
@ -23,7 +23,8 @@ from calibre.gui2.tweak_book import (
|
||||
editors, tprefs, update_mark_text_action
|
||||
)
|
||||
from calibre.gui2.tweak_book.editor import (
|
||||
CSS_PROPERTY, LINK_PROPERTY, SPELL_PROPERTY, TAG_NAME_PROPERTY
|
||||
CLASS_ATTRIBUTE_PROPERTY, CSS_PROPERTY, LINK_PROPERTY, SPELL_PROPERTY,
|
||||
TAG_NAME_PROPERTY
|
||||
)
|
||||
from calibre.gui2.tweak_book.editor.help import help_url
|
||||
from calibre.gui2.tweak_book.editor.text import TextEdit
|
||||
@ -142,6 +143,7 @@ class Editor(QMainWindow):
|
||||
word_ignored = pyqtSignal(object, object)
|
||||
link_clicked = pyqtSignal(object)
|
||||
class_clicked = pyqtSignal(object)
|
||||
rename_class = pyqtSignal(object)
|
||||
smart_highlighting_updated = pyqtSignal()
|
||||
|
||||
def __init__(self, syntax, parent=None):
|
||||
@ -579,6 +581,12 @@ class Editor(QMainWindow):
|
||||
href = self.editor.text_for_range(origc.block(), origr)
|
||||
m.addAction(_('Open %s') % href, partial(self.link_clicked.emit, href))
|
||||
|
||||
if origr is not None and origr.format.property(CLASS_ATTRIBUTE_PROPERTY):
|
||||
cls = self.editor.class_for_position(pos)
|
||||
if cls:
|
||||
class_name = cls['class']
|
||||
m.addAction(_('Rename the class {}').format(class_name), partial(self.rename_class.emit, class_name))
|
||||
|
||||
if origr is not None and (origr.format.property(TAG_NAME_PROPERTY) or origr.format.property(CSS_PROPERTY)):
|
||||
word = self.editor.text_for_range(origc.block(), origr)
|
||||
item_type = 'tag_name' if origr.format.property(TAG_NAME_PROPERTY) else 'css_property'
|
||||
|
Loading…
x
Reference in New Issue
Block a user