mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Edit book: Add a tool to split the tag at the current cursor position,
creating a new tag with the same style and class attributes. To add the tool go to the Toolbars section in the editor preferences. Fixes #1912958 [Feature: Merge/split paragraphs tool in ebook editor](https://bugs.launchpad.net/calibre/+bug/1912958)
This commit is contained in:
parent
9d41474d9d
commit
61efa239ec
54
imgsrc/split.svg
Normal file
54
imgsrc/split.svg
Normal file
@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
height="300px"
|
||||
width="300px"
|
||||
fill="#000000"
|
||||
version="1.1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 97.8 97.8"
|
||||
enable-background="new 0 0 97.8 97.8"
|
||||
xml:space="preserve"
|
||||
id="svg4"
|
||||
sodipodi:docname="split.svg"
|
||||
inkscape:version="1.0.2 (e86c870879, 2021-01-15)"><metadata
|
||||
id="metadata10"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs8" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1400"
|
||||
id="namedview6"
|
||||
showgrid="false"
|
||||
inkscape:zoom="3.42"
|
||||
inkscape:cx="69.444444"
|
||||
inkscape:cy="150"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="40"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg4" /><g
|
||||
id="g852"><path
|
||||
d="m 19.3408,34.258128 c -0.162508,-4.4e-4 -0.314692,0.04583 -0.448514,0.148541 L 0.52457424,48.433157 c -0.13860367,0.105527 -0.25984757,0.258831 -0.29900965,0.424402 -0.0130483,0.05519 0,0.111894 0,0.169761 0,0.231446 0.11420026,0.453466 0.29900965,0.594163 L 18.913644,63.669191 c 0.13453,0.102691 0.265721,0.14854 0.427156,0.14854 0.159311,0 0.33605,-0.04795 0.469872,-0.14854 0.269053,-0.201203 0.379495,-0.579601 0.256293,-0.891244 l -3.694899,-9.273185 h 21.656811 c 0.41421,0 0.747523,-0.331166 0.747523,-0.742704 v -7.448256 c 0,-0.411537 -0.333313,-0.763924 -0.747523,-0.763924 H 16.372066 l 3.694899,-9.294406 c 0.123909,-0.310939 0.01277,-0.647607 -0.256293,-0.848803 -0.13453,-0.10095 -0.307363,-0.148101 -0.469872,-0.148541 z"
|
||||
id="path839"
|
||||
style="fill:#2caf45;fill-opacity:1;stroke-width:1.22124" /><path
|
||||
d="m 78.459201,34.130807 c -0.162511,4.41e-4 -0.335342,0.04758 -0.469872,0.148542 -0.269061,0.201195 -0.380203,0.537863 -0.256294,0.848803 l 3.6949,9.294406 H 59.771122 c -0.41421,0 -0.747524,0.352387 -0.747524,0.763924 v 7.448256 c 0,0.411538 0.333314,0.742702 0.747524,0.742702 h 21.656813 l -3.6949,9.273186 c -0.123198,0.311643 -0.01278,0.690042 0.256294,0.891245 0.13382,0.100597 0.31056,0.14854 0.469872,0.14854 0.161438,0 0.292625,-0.04586 0.427156,-0.14854 L 97.275426,49.494163 c 0.184798,-0.140698 0.299009,-0.362717 0.299009,-0.594164 0,-0.05786 0.01305,-0.114572 0,-0.16976 -0.03916,-0.165573 -0.160413,-0.31888 -0.299009,-0.424402 L 78.907715,34.279349 c -0.13382,-0.102716 -0.286003,-0.148981 -0.448514,-0.148542 z"
|
||||
id="path837"
|
||||
style="fill:#2caf45;fill-opacity:1;stroke-width:1.22124" /><path
|
||||
d="m 43.902273,11.748602 c -0.781685,0 -1.409615,1.11798 -1.409615,2.509715 v 69.283364 c 0,1.391734 0.627222,2.510931 1.409615,2.509716 h 9.546938 c 0.781686,0 1.430973,-1.117982 1.430973,-2.509716 V 14.258317 c 0,-1.391735 -0.649287,-2.509715 -1.430973,-2.509715 z"
|
||||
id="path2"
|
||||
style="fill:#2271d5;fill-opacity:1;stroke-width:1.63481" /></g></svg>
|
After Width: | Height: | Size: 3.5 KiB |
BIN
resources/images/split.png
Normal file
BIN
resources/images/split.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
@ -5,23 +5,27 @@
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
import sys, re
|
||||
from operator import itemgetter
|
||||
from itertools import chain
|
||||
|
||||
import re
|
||||
import sys
|
||||
from contextlib import contextmanager
|
||||
from css_parser import parseStyle
|
||||
from PyQt5.Qt import QTextEdit, Qt, QTextCursor
|
||||
from itertools import chain
|
||||
from operator import itemgetter
|
||||
from PyQt5.Qt import Qt, QTextCursor, QTextEdit
|
||||
|
||||
from calibre import prepare_string_for_xml, xml_entity_to_unicode
|
||||
from calibre.ebooks.oeb.polish.container import OEB_DOCS
|
||||
from calibre.ebooks.oeb.base import css_text
|
||||
from calibre.ebooks.oeb.polish.container import OEB_DOCS
|
||||
from calibre.gui2 import error_dialog
|
||||
from calibre.gui2.tweak_book.editor.syntax.html import ATTR_NAME, ATTR_END, ATTR_START, ATTR_VALUE
|
||||
from calibre.gui2.tweak_book import tprefs, current_container
|
||||
from calibre.gui2.tweak_book import current_container, tprefs
|
||||
from calibre.gui2.tweak_book.editor.smarts import NullSmarts
|
||||
from calibre.gui2.tweak_book.editor.smarts.utils import (
|
||||
no_modifiers, get_leading_whitespace_on_block, get_text_before_cursor,
|
||||
get_text_after_cursor, smart_home, smart_backspace, smart_tab, expand_tabs)
|
||||
expand_tabs, get_leading_whitespace_on_block, get_text_after_cursor,
|
||||
get_text_before_cursor, no_modifiers, smart_backspace, smart_home, smart_tab
|
||||
)
|
||||
from calibre.gui2.tweak_book.editor.syntax.html import (
|
||||
ATTR_END, ATTR_NAME, ATTR_START, ATTR_VALUE
|
||||
)
|
||||
from calibre.utils.icu import utf16_length
|
||||
from polyglot.builtins import unicode_type
|
||||
|
||||
@ -214,11 +218,20 @@ def find_closing_tag(tag, max_tags=sys.maxsize):
|
||||
def select_tag(cursor, tag):
|
||||
cursor.setPosition(tag.start_block.position() + tag.start_offset)
|
||||
cursor.setPosition(tag.end_block.position() + tag.end_offset + 1, QTextCursor.MoveMode.KeepAnchor)
|
||||
return unicode_type(cursor.selectedText()).replace(PARAGRAPH_SEPARATOR, '\n').rstrip('\0')
|
||||
return cursor.selectedText().replace(PARAGRAPH_SEPARATOR, '\n').rstrip('\0')
|
||||
|
||||
|
||||
@contextmanager
|
||||
def edit_block(cursor):
|
||||
cursor.beginEditBlock()
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
cursor.endEditBlock()
|
||||
|
||||
|
||||
def rename_tag(cursor, opening_tag, closing_tag, new_name, insert=False):
|
||||
cursor.beginEditBlock()
|
||||
with edit_block(cursor):
|
||||
text = select_tag(cursor, closing_tag)
|
||||
if insert:
|
||||
text = '</%s>%s' % (new_name, text)
|
||||
@ -231,7 +244,26 @@ def rename_tag(cursor, opening_tag, closing_tag, new_name, insert=False):
|
||||
else:
|
||||
text = re.sub(r'^<\s*[a-zA-Z0-9]+', '<%s' % new_name, text)
|
||||
cursor.insertText(text)
|
||||
cursor.endEditBlock()
|
||||
|
||||
|
||||
def split_tag(cursor, opening_tag, closing_tag):
|
||||
pos = cursor.position()
|
||||
with edit_block(cursor):
|
||||
open_text = select_tag(cursor, opening_tag)
|
||||
open_text = re.sub(r'''\bid\s*=\s*['"].*?['"]''', '', open_text)
|
||||
open_text = re.sub(r'\s+', ' ', open_text)
|
||||
tag_name = re.search(r'<\s*(\S+)', open_text).group(1).lower()
|
||||
is_block = tag_name in BLOCK_TAG_NAMES
|
||||
prefix = ''
|
||||
if is_block:
|
||||
cursor.setPosition(cursor.anchor())
|
||||
cursor.movePosition(QTextCursor.MoveOperation.StartOfLine, QTextCursor.MoveMode.KeepAnchor)
|
||||
x = cursor.selectedText()
|
||||
if x and not x.strip():
|
||||
prefix = PARAGRAPH_SEPARATOR + x
|
||||
close_text = select_tag(cursor, closing_tag)
|
||||
cursor.setPosition(pos)
|
||||
cursor.insertText(f'{close_text}{prefix}{open_text}')
|
||||
|
||||
|
||||
def ensure_not_within_tag_definition(cursor, forward=True):
|
||||
@ -397,9 +429,28 @@ class Smarts(NullSmarts):
|
||||
' before trying to rename tags.') % tag.name, show=True)
|
||||
rename_tag(c, tag, closing_tag, new_name, insert=tag.name in {'body', 'td', 'th', 'li'})
|
||||
else:
|
||||
return error_dialog(editor, _('No found'), _(
|
||||
return error_dialog(editor, _('No tag found'), _(
|
||||
'No suitable block level tag was found to rename'), show=True)
|
||||
|
||||
def split_tag(self, editor):
|
||||
editor.highlighter.join()
|
||||
c = editor.textCursor()
|
||||
block, offset = c.block(), c.positionInBlock()
|
||||
tag, closing = find_tag_definition(block, offset)
|
||||
if tag is not None:
|
||||
return error_dialog(editor, _('Cursor inside tag'), _(
|
||||
'Cannot split as the cursor is inside the tag definition'), show=True)
|
||||
tag = find_closest_containing_tag(block, offset)
|
||||
if tag is None:
|
||||
return error_dialog(editor, _('No tag found'), _(
|
||||
'No suitable tag was found to split'), show=True)
|
||||
closing_tag = find_closing_tag(tag)
|
||||
if closing_tag is None:
|
||||
return error_dialog(editor, _('Invalid HTML'), _(
|
||||
'There is an unclosed %s tag. You should run the Fix HTML tool'
|
||||
' before trying to split tags.') % tag.name, show=True)
|
||||
split_tag(c, tag, closing_tag)
|
||||
|
||||
def get_smart_selection(self, editor, update=True):
|
||||
editor.highlighter.join()
|
||||
cursor = editor.textCursor()
|
||||
|
@ -900,6 +900,10 @@ version="1.1" width="100%%" height="100%%" viewBox="0 0 {w} {h}" preserveAspectR
|
||||
if hasattr(self.smarts, 'remove_tag'):
|
||||
self.smarts.remove_tag(self)
|
||||
|
||||
def split_tag(self):
|
||||
if hasattr(self.smarts, 'split_tag'):
|
||||
self.smarts.split_tag(self)
|
||||
|
||||
def keyPressEvent(self, ev):
|
||||
if ev.key() == Qt.Key.Key_X and ev.modifiers() == Qt.KeyboardModifier.AltModifier:
|
||||
if self.replace_possible_unicode_sequence():
|
||||
|
@ -120,6 +120,9 @@ def register_text_editor_actions(_reg, palette):
|
||||
ac = reg('trash.png', _('Remove &tag'), ('remove_tag',), 'remove-tag', ('Ctrl+>'), _('Remove tag'), syntaxes=('html', 'xml'))
|
||||
ac.setToolTip(_('<h3>Remove tag</h3>Remove the currently highlighted tag'))
|
||||
|
||||
ac = reg('split.png', _('&Split tag'), ('split_tag',), 'split-tag', ('Ctrl+Alt+>'), _('Split current tag'), syntaxes=('html', 'xml'))
|
||||
ac.setToolTip(_('<h3>Split tag</h3>Split the current tag at the cursor position'))
|
||||
|
||||
editor_toolbar_actions['html']['fix-html-current'] = actions['fix-html-current']
|
||||
for s in ('xml', 'html', 'css'):
|
||||
editor_toolbar_actions[s]['pretty-current'] = actions['pretty-current']
|
||||
|
Loading…
x
Reference in New Issue
Block a user