diff --git a/src/pyj/read_book/globals.pyj b/src/pyj/read_book/globals.pyj index 9af3272eef..1104061e79 100644 --- a/src/pyj/read_book/globals.pyj +++ b/src/pyj/read_book/globals.pyj @@ -119,3 +119,9 @@ annot_id_uuid_map = {} def clear_annot_id_uuid_map(): nonlocal annot_id_uuid_map annot_id_uuid_map = {} + + +def is_dark_theme(set_val): + if set_val?: + is_dark_theme.ans = set_val + return v'!!is_dark_theme.ans' diff --git a/src/pyj/read_book/highlights.pyj b/src/pyj/read_book/highlights.pyj index 828f75a20e..c2c42d20fc 100644 --- a/src/pyj/read_book/highlights.pyj +++ b/src/pyj/read_book/highlights.pyj @@ -38,7 +38,7 @@ builtin_decorations_light = builtin_decorations_dark = { } -def friendly_name(kind, which): +def builtin_friendly_name(kind, which): if kind is 'color': return { 'yellow': _('Yellow highlight'), @@ -49,7 +49,7 @@ def friendly_name(kind, which): }[which] or _('Unknown highlight') return { 'wavy': _('Red wavy underline'), - 'strikeout': _('Red underline'), + 'strikeout': _('Red strikeout'), }[which] or _('Unknown underline') @@ -124,7 +124,7 @@ class HighlightStyle: style.textDecorationStyle = tds if tdc: style.textDecorationColor = tdc - return + return container bg = None if s.type is 'builtin': @@ -136,6 +136,7 @@ class HighlightStyle: if bg: style.backgroundColor = bg style.borderRadius = f'{br}{ICON_SIZE_UNIT}' + return container def highlight_shade(self, is_dark): s = self.style @@ -158,7 +159,7 @@ class HighlightStyle: def friendly_name(self): s = self.style if s.type is 'builtin': - return friendly_name(s.kind, s.which) + return builtin_friendly_name(s.kind, s.which) return s.friendly_name or _('Custom style') @@ -206,22 +207,14 @@ def custom_styles_equal(a, b): return True -class StyleCollection: - - def __init__(self): - custom_highlight_styles = get_session_data().get('custom_highlight_styles') - self.styles = [] - self.style_id_counter = 0 - - def add(raw): - hs = HighlightStyle(raw) - hs.style_id = v'++self.style_id_counter' - self.styles.push(hs) - - for raw in custom_highlight_styles: - add(raw) - for raw in all_builtin_styles(): - add(raw) +def all_styles(): + ans = v'[]' + custom_highlight_styles = get_session_data().get('custom_highlight_styles') + for raw in custom_highlight_styles: + ans.push(HighlightStyle(raw)) + for raw in all_builtin_styles(): + ans.push(HighlightStyle(raw)) + return ans class AddStyle: # {{{ diff --git a/src/pyj/read_book/prefs/selection.pyj b/src/pyj/read_book/prefs/selection.pyj index 8076bc3319..a99456c024 100644 --- a/src/pyj/read_book/prefs/selection.pyj +++ b/src/pyj/read_book/prefs/selection.pyj @@ -7,6 +7,8 @@ from gettext import gettext as _ from book_list.globals import get_session_data from dom import clear, svgicon, unique_id +from read_book.globals import is_dark_theme +from read_book.highlights import all_styles from read_book.prefs.utils import create_button_box from read_book.selection_bar import all_actions from session import defaults @@ -17,12 +19,16 @@ CONTAINER = unique_id('selection-settings') def set_actions(use_defaults): c = get_container() adef = all_actions() - actions = defaults.selection_bar_actions if use_defaults else get_session_data().get('selection_bar_actions') + sd = get_session_data() + actions = defaults.selection_bar_actions if use_defaults else sd.get('selection_bar_actions') current = [x for x in actions if adef[x]] c.querySelector('.current-actions').dataset.actions = JSON.stringify(current) available_actions = [x for x in adef if current.indexOf(x) is -1] c.querySelector('.available-actions').dataset.actions = JSON.stringify(available_actions) update_action_tables() + quick_actions = defaults.selection_bar_quick_highlights if use_defaults else sd.get('selection_bar_quick_highlights') + c.querySelector('.quick-actions').dataset.actions = JSON.stringify(quick_actions) + update_quick_action_table() def restore_defaults(): @@ -113,6 +119,32 @@ def update_action_tables(): build_action_table(current, False) +def update_quick_action_table(): + c = get_container().querySelector('.quick-actions') + clear(c) + c.style.display = 'flex' + c.style.flexWrap = 'wrap' + current = {x: True for x in JSON.parse(c.dataset.actions)} + for hs in all_styles(): + c.appendChild(E.label( + style='margin: 1ex; display: flex; align-contents: center', + hs.make_swatch(E.span(), is_dark_theme()), + '\xa0', + hs.friendly_name, + '\xa0', + E.input(type='checkbox', value=hs.key, checked=current[hs.key]), + )) + + +def selected_quick_actions(): + ans = v'[]' + c = get_container().querySelector('.quick-actions') + for inp in c.querySelectorAll('input:checked'): + if inp.value: + ans.push(inp.value) + return ans + + def create_selection_panel(container, apply_func, cancel_func): container.appendChild(E.div(id=CONTAINER, style='margin: 1rem')) container = container.lastChild @@ -144,6 +176,11 @@ def create_selection_panel(container, apply_func, cancel_func): E.h3(_('Available actions')), E.div(class_='available-actions') )) + container.appendChild(E.div(style='padding: 1ex; border-bottom: solid 1px; margin-bottom: 1ex', + E.h3(_('Quick highlight actions')), + E.div(_('Choose highlight styles that will have dedicated buttons in the selection bar to create highlights with a single click')), + E.div(class_='quick-actions'), + )) set_actions() container.appendChild(create_button_box(restore_defaults, apply_func, cancel_func)) @@ -171,5 +208,9 @@ def commit_selection(onchange): if list(actions) != list(sd.get('selection_bar_actions')): changed = True sd.set('selection_bar_actions', actions) + quick_highlights = selected_quick_actions() + if list(quick_highlights) != list(sd.get('selection_bar_quick_highlights')): + changed = True + sd.set('selection_bar_quick_highlights', quick_highlights) if changed: onchange() diff --git a/src/pyj/read_book/selection_bar.pyj b/src/pyj/read_book/selection_bar.pyj index f013b89bc9..a963e01e37 100644 --- a/src/pyj/read_book/selection_bar.pyj +++ b/src/pyj/read_book/selection_bar.pyj @@ -11,10 +11,13 @@ from book_list.theme import get_color from dom import clear, svgicon, unique_id from modals import error_dialog, question_dialog from read_book.globals import runtime, ui_operations -from read_book.highlights import ICON_SIZE, EditNotesAndColors, HighlightStyle +from read_book.highlights import ( + ICON_SIZE, EditNotesAndColors, HighlightStyle, all_styles +) from read_book.shortcuts import shortcut_for_key_event DRAG_SCROLL_ZONE_MIN_HEIGHT = 10 +BUTTON_MARGIN = '0.5rem' # Utils {{{ @@ -291,7 +294,7 @@ class SelectionBar: self.view.focus_iframe() ) ans.classList.add('simple-link') - ans.style.marginLeft = ans.style.marginRight = '0.5rem' + ans.style.marginLeft = ans.style.marginRight = BUTTON_MARGIN return ans actions = all_actions() @@ -300,9 +303,31 @@ class SelectionBar: ac = actions[acname] if ac and (not ac.needs_highlight or v'!!annot_id'): bar.appendChild(cb(ac, self[ac.function_name])) + selection_bar_quick_highlights = sd.get('selection_bar_quick_highlights') + if selection_bar_quick_highlights?.length: + self.show_quick_highlight_buttons(bar, selection_bar_quick_highlights) self.show_notes(bar_container, notes) return bar_container + def show_quick_highlight_buttons(self, bar, actions): + all = {x.key:x for x in all_styles()} + actions = [a for a in actions if all[a]] + if not actions.length: + return + bar.appendChild(E.div( + style=f'background: currentColor; width: 1px; height: {ICON_SIZE}; margin-left: {BUTTON_MARGIN}; margin-right: {BUTTON_MARGIN}' + )) + dark = self.view.current_color_scheme.is_dark_theme + for key in actions: + hs = all[key] + sw = E.div( + class_='simple-link', style=f'margin-left: {BUTTON_MARGIN}; margin-right: {BUTTON_MARGIN}', + title=_('Highlight using: {}').format(hs.friendly_name), + onclick=self.quick_highlight_with_style.bind(None, hs), + ) + hs.make_swatch(sw, dark) + bar.appendChild(sw) + def show_notes(self, bar, notes): notes = (notes or "").strip() if not notes: @@ -793,6 +818,10 @@ class SelectionBar: self.state = WAITING self.update_position() + def quick_highlight_with_style(self, hs): + self.current_highlight_style = hs + self.quick_highlight() + def remove_highlight(self): annot_id = self.view.currently_showing.selection.annot_id if annot_id: diff --git a/src/pyj/read_book/view.pyj b/src/pyj/read_book/view.pyj index 092b08168f..da858636e9 100644 --- a/src/pyj/read_book/view.pyj +++ b/src/pyj/read_book/view.pyj @@ -17,8 +17,8 @@ from read_book.annotations import AnnotationsManager from read_book.bookmarks import create_new_bookmark from read_book.content_popup import ContentPopupOverlay from read_book.globals import ( - current_book, rtl_page_progression, runtime, set_current_spine_item, - ui_operations + current_book, is_dark_theme, rtl_page_progression, runtime, + set_current_spine_item, ui_operations ) from read_book.goto import get_next_section from read_book.open_book import add_book_to_recently_viewed @@ -301,6 +301,7 @@ class View: if runtime.is_standalone_viewer: document.documentElement.addEventListener('keydown', self.handle_keypress, {'passive': False}) set_ui_colors(self.current_color_scheme.is_dark_theme) + is_dark_theme(self.current_color_scheme.is_dark_theme) self.iframe_wrapper = IframeWrapper( handlers, document.getElementById(iframe_id), entry_point, _('Bootstrapping book reader...'), f'{runtime.FAKE_PROTOCOL}://{runtime.SANDBOX_HOST}/book/__index__') @@ -741,6 +742,7 @@ class View: self.current_color_scheme = ans = resolve_color_scheme() if runtime.is_standalone_viewer: set_ui_colors(self.current_color_scheme.is_dark_theme) + is_dark_theme(self.current_color_scheme.is_dark_theme) for which in 'left top right bottom'.split(' '): m = document.getElementById('book-{}-margin'.format(which)) s = m.style diff --git a/src/pyj/session.pyj b/src/pyj/session.pyj index b1036bdeea..17743364d5 100644 --- a/src/pyj/session.pyj +++ b/src/pyj/session.pyj @@ -67,6 +67,7 @@ defaults = { 'show_selection_bar': True, 'net_search_url': 'https://google.com/search?q={q}', 'selection_bar_actions': v"['copy', 'lookup', 'highlight', 'remove_highlight', 'search_net', 'clear']", + 'selection_bar_quick_highlights': v"[]", } is_local_setting = {