mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Add basic formatting buttons to comments editor toolbar on EM page
This commit is contained in:
parent
3b464dc99b
commit
88d4772ec8
1
imgsrc/srv/bold.svg
Normal file
1
imgsrc/srv/bold.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M747 1521q74 32 140 32 376 0 376-335 0-114-41-180-27-44-61.5-74t-67.5-46.5-80.5-25-84-10.5-94.5-2q-73 0-101 10 0 53-.5 159t-.5 158q0 8-1 67.5t-.5 96.5 4.5 83.5 12 66.5zm-14-746q42 7 109 7 82 0 143-13t110-44.5 74.5-89.5 25.5-142q0-70-29-122.5t-79-82-108-43.5-124-14q-50 0-130 13 0 50 4 151t4 152q0 27-.5 80t-.5 79q0 46 1 69zm-541 889l2-94q15-4 85-16t106-27q7-12 12.5-27t8.5-33.5 5.5-32.5 3-37.5.5-34v-65.5q0-982-22-1025-4-8-22-14.5t-44.5-11-49.5-7-48.5-4.5-30.5-3l-4-83q98-2 340-11.5t373-9.5q23 0 68.5.5t67.5.5q70 0 136.5 13t128.5 42 108 71 74 104.5 28 137.5q0 52-16.5 95.5t-39 72-64.5 57.5-73 45-84 40q154 35 256.5 134t102.5 248q0 100-35 179.5t-93.5 130.5-138 85.5-163.5 48.5-176 14q-44 0-132-3t-132-3q-106 0-307 11t-231 12z"/></svg>
|
After Width: | Height: | Size: 833 B |
1
imgsrc/srv/italic.svg
Normal file
1
imgsrc/srv/italic.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M384 1662l17-85q6-2 81.5-21.5t111.5-37.5q28-35 41-101 1-7 62-289t114-543.5 52-296.5v-25q-24-13-54.5-18.5t-69.5-8-58-5.5l19-103q33 2 120 6.5t149.5 7 120.5 2.5q48 0 98.5-2.5t121-7 98.5-6.5q-5 39-19 89-30 10-101.5 28.5t-108.5 33.5q-8 19-14 42.5t-9 40-7.5 45.5-6.5 42q-27 148-87.5 419.5t-77.5 355.5q-2 9-13 58t-20 90-16 83.5-6 57.5l1 18q17 4 185 31-3 44-16 99-11 0-32.5 1.5t-32.5 1.5q-29 0-87-10t-86-10q-138-2-206-2-51 0-143 9t-121 11z"/></svg>
|
After Width: | Height: | Size: 540 B |
1
imgsrc/srv/strikethrough.svg
Normal file
1
imgsrc/srv/strikethrough.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M1760 896q14 0 23 9t9 23v64q0 14-9 23t-23 9h-1728q-14 0-23-9t-9-23v-64q0-14 9-23t23-9h1728zm-1277-64q-28-35-51-80-48-97-48-188 0-181 134-309 133-127 393-127 50 0 167 19 66 12 177 48 10 38 21 118 14 123 14 183 0 18-5 45l-12 3-84-6-14-2q-50-149-103-205-88-91-210-91-114 0-182 59-67 58-67 146 0 73 66 140t279 129q69 20 173 66 58 28 95 52h-743zm507 256h411q7 39 7 92 0 111-41 212-23 55-71 104-37 35-109 81-80 48-153 66-80 21-203 21-114 0-195-23l-140-40q-57-16-72-28-8-8-8-22v-13q0-108-2-156-1-30 0-68l2-37v-44l102-2q15 34 30 71t22.5 56 12.5 27q35 57 80 94 43 36 105 57 59 22 132 22 64 0 139-27 77-26 122-86 47-61 47-129 0-84-81-157-34-29-137-71z"/></svg>
|
After Width: | Height: | Size: 750 B |
1
imgsrc/srv/underline.svg
Normal file
1
imgsrc/srv/underline.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M176 223q-37-2-45-4l-3-88q13-1 40-1 60 0 112 4 132 7 166 7 86 0 168-3 116-4 146-5 56 0 86-2l-1 14 2 64v9q-60 9-124 9-60 0-79 25-13 14-13 132 0 13 .5 32.5t.5 25.5l1 229 14 280q6 124 51 202 35 59 96 92 88 47 177 47 104 0 191-28 56-18 99-51 48-36 65-64 36-56 53-114 21-73 21-229 0-79-3.5-128t-11-122.5-13.5-159.5l-4-59q-5-67-24-88-34-35-77-34l-100 2-14-3 2-86h84l205 10q76 3 196-10l18 2q6 38 6 51 0 7-4 31-45 12-84 13-73 11-79 17-15 15-15 41 0 7 1.5 27t1.5 31q8 19 22 396 6 195-15 304-15 76-41 122-38 65-112 123-75 57-182 89-109 33-255 33-167 0-284-46-119-47-179-122-61-76-83-195-16-80-16-237v-333q0-188-17-213-25-36-147-39zm1488 1409v-64q0-14-9-23t-23-9h-1472q-14 0-23 9t-9 23v64q0 14 9 23t23 9h1472q14 0 23-9t9-23z"/></svg>
|
After Width: | Height: | Size: 822 B |
@ -5,9 +5,60 @@ from __python__ import bound_methods, hash_literals
|
||||
from elementmaker import E
|
||||
from gettext import gettext as _
|
||||
|
||||
from dom import clear, ensure_id
|
||||
from book_list.theme import get_color
|
||||
from dom import add_extra_css, build_rule, clear, ensure_id, svgicon
|
||||
from iframe_comm import IframeClient, IframeWrapper
|
||||
|
||||
CLASS_NAME = 'comments-editor'
|
||||
TOOLBAR_CLASS = 'comments-editor-toolbar'
|
||||
|
||||
add_extra_css(def():
|
||||
sel = '.' + TOOLBAR_CLASS + ' '
|
||||
style = ''
|
||||
style += build_rule(sel, display='flex', flex_wrap='wrap', padding_top='0.25ex', padding_bottom='0.25ex')
|
||||
sel += ' > div'
|
||||
style += build_rule(sel, padding='0.5ex', margin_right='0.5ex', cursor='pointer')
|
||||
style += build_rule(sel + ':hover', color='red')
|
||||
style += build_rule(sel + '.activated', color=get_color('window-background'), background=get_color('window-foreground'))
|
||||
sel += '.sep'
|
||||
style += build_rule(sel, border_left='solid 2px currentColor', cursor='auto')
|
||||
style += build_rule(sel + ':hover', color='currentColor')
|
||||
return style
|
||||
)
|
||||
|
||||
|
||||
def all_editor_actions():
|
||||
if not all_editor_actions.ans:
|
||||
all_editor_actions.ans = {
|
||||
'bold': {
|
||||
'icon': 'bold',
|
||||
'title': _('Bold'),
|
||||
'execute': def (editor, activated):
|
||||
editor.exec_command('bold')
|
||||
},
|
||||
'italic': {
|
||||
'icon': 'italic',
|
||||
'title': _('Italic'),
|
||||
'execute': def (editor, activated):
|
||||
editor.exec_command('italic')
|
||||
},
|
||||
'underline': {
|
||||
'icon': 'underline',
|
||||
'title': _('Underline'),
|
||||
'execute': def (editor, activated):
|
||||
editor.exec_command('underline')
|
||||
},
|
||||
'strikethrough': {
|
||||
'icon': 'strikethrough',
|
||||
'title': _('Strikethrough'),
|
||||
'execute': def (editor, activated):
|
||||
editor.exec_command('strikeThrough')
|
||||
},
|
||||
|
||||
}
|
||||
return all_editor_actions.ans
|
||||
|
||||
|
||||
|
||||
class CommentsEditorBoss:
|
||||
|
||||
@ -16,12 +67,15 @@ class CommentsEditorBoss:
|
||||
'initialize': self.initialize,
|
||||
'set_html': self.set_html,
|
||||
'get_html': self.get_html,
|
||||
'exec_command': self.exec_command,
|
||||
'focus': self.focus,
|
||||
}
|
||||
self.comm = IframeClient(handlers)
|
||||
|
||||
def initialize(self, data):
|
||||
window.onerror = self.onerror
|
||||
clear(document.body)
|
||||
document.execCommand("defaultParagraphSeparator", False, "div")
|
||||
document.body.style.margin = '0'
|
||||
document.body.style.padding = '0'
|
||||
document.documentElement.style.height = document.body.style.height = '100%'
|
||||
@ -29,6 +83,12 @@ class CommentsEditorBoss:
|
||||
document.body.style.fontFamily = window.default_font_family
|
||||
document.body.appendChild(E.div(style='width: 100%; height: 100%; padding: 0; margin: 0; border: solid 3px transparent; box-sizing: border-box'))
|
||||
document.body.lastChild.contentEditable = True
|
||||
document.body.lastChild.addEventListener('keyup', self.update_state)
|
||||
document.body.lastChild.addEventListener('mouseup', self.update_state)
|
||||
document.body.lastChild.focus()
|
||||
self.update_state()
|
||||
|
||||
def focus(self):
|
||||
document.body.lastChild.focus()
|
||||
|
||||
def onerror(self, msg, script_url, line_number, column_number, error_object):
|
||||
@ -46,6 +106,16 @@ class CommentsEditorBoss:
|
||||
def get_html(self, data):
|
||||
self.comm.send_message('html', html=document.body.lastChild.innerHTML)
|
||||
|
||||
def exec_command(self, data):
|
||||
document.execCommand(data.name, False, data.value)
|
||||
self.update_state()
|
||||
|
||||
def update_state(self):
|
||||
state = {name: document.queryCommandState(name) for name in 'bold italic underline'.split(' ')}
|
||||
state.strikethrough = document.queryCommandState('strikeThrough')
|
||||
self.comm.send_message('update_state', state=state)
|
||||
|
||||
|
||||
|
||||
registry = {}
|
||||
|
||||
@ -63,6 +133,7 @@ class Editor:
|
||||
handlers = {
|
||||
'ready': self.on_iframe_ready,
|
||||
'html': self.on_html_received,
|
||||
'update_state': self.update_state,
|
||||
}
|
||||
self.iframe_wrapper = IframeWrapper(handlers, iframe, 'book_list.comments_editor', _('Loading comments editor...'))
|
||||
self.id = ensure_id(iframe)
|
||||
@ -77,6 +148,11 @@ class Editor:
|
||||
self.iframe_wrapper.destroy()
|
||||
self.get_html_callbacks = v'[]'
|
||||
|
||||
def focus(self):
|
||||
self.iframe.contentWindow.focus()
|
||||
if self.ready:
|
||||
self.iframe_wrapper.send_message('focus')
|
||||
|
||||
@property
|
||||
def iframe(self):
|
||||
return self.iframe_wrapper.iframe
|
||||
@ -109,6 +185,19 @@ class Editor:
|
||||
f(data.html)
|
||||
self.get_html_callbacks = v'[]'
|
||||
|
||||
def exec_command(self, name, value=None):
|
||||
if self.ready:
|
||||
self.iframe_wrapper.send_message('exec_command', name=name, value=value)
|
||||
|
||||
def update_state(self, data):
|
||||
c = self.iframe.closest('.' + CLASS_NAME)
|
||||
for name in Object.keys(data.state):
|
||||
div = c.querySelector(f'.{TOOLBAR_CLASS} > [name="{name}"]')
|
||||
if div:
|
||||
if data.state[name]:
|
||||
div.classList.add('activated')
|
||||
else:
|
||||
div.classList.remove('activated')
|
||||
|
||||
|
||||
def create_editor():
|
||||
@ -118,18 +207,47 @@ def create_editor():
|
||||
return iframe, editor
|
||||
|
||||
|
||||
def action_activated(editor_id, ac_name, evt):
|
||||
editor = registry[editor_id]
|
||||
if not editor:
|
||||
return
|
||||
action = all_editor_actions()[ac_name]
|
||||
if not action:
|
||||
return
|
||||
button = evt.currentTarget
|
||||
action.execute(editor, button.classList.contains('activated'))
|
||||
editor.focus()
|
||||
|
||||
|
||||
def add_action(toolbar, ac_name, action, editor_id):
|
||||
b = E.div(svgicon(action.icon), title=action.title, onclick=action_activated.bind(None, editor_id, ac_name), name=ac_name)
|
||||
toolbar.appendChild(b)
|
||||
|
||||
|
||||
def create_comments_editor(container):
|
||||
iframe, editor = create_editor()
|
||||
toolbar1 = E.div('TODO: add toolbar', style='flex-grow: 0')
|
||||
toolbars = E.div(style='flex-grow: 0')
|
||||
toolbar1 = E.div(class_=TOOLBAR_CLASS)
|
||||
toolbars.appendChild(toolbar1)
|
||||
acmap = all_editor_actions()
|
||||
for ac_name in 'bold italic underline strikethrough |'.split(' '):
|
||||
if acmap[ac_name]:
|
||||
add_action(toolbar1, ac_name, acmap[ac_name], editor.id)
|
||||
else:
|
||||
toolbar1.appendChild(E.div(class_='sep'))
|
||||
|
||||
container.setAttribute('style', (container.getAttribute('style') or '') + ';display: flex; flex-direction: column; align-items: stretch')
|
||||
container.appendChild(toolbar1)
|
||||
container.appendChild(toolbars)
|
||||
container.appendChild(iframe)
|
||||
container.classList.add(CLASS_NAME)
|
||||
return editor
|
||||
|
||||
|
||||
def focus_comments_editor(container):
|
||||
iframe = container.querySelector('iframe')
|
||||
iframe.contentWindow.focus()
|
||||
editor = registry[iframe.getAttribute('id')]
|
||||
if editor:
|
||||
editor.focus()
|
||||
|
||||
|
||||
def set_comments_html(container, html):
|
||||
|
Loading…
x
Reference in New Issue
Block a user