diff --git a/src/book-viewer.js b/src/book-viewer.js index 0611b26b..2e2735ef 100644 --- a/src/book-viewer.js +++ b/src/book-viewer.js @@ -19,6 +19,7 @@ import './navbar.js' import { AnnotationPopover, AnnotationModel, BookmarkModel, exportAnnotations, } from './annotations.js' +import { SelectionPopover } from './selection-tools.js' import { ImageViewer } from './image-viewer.js' import { makeBookInfoWindow } from './book-info.js' import { getURIStore, getBookList } from './library.js' @@ -577,12 +578,6 @@ GObject.registerClass({ grab_focus() { return this.#webView.grab_focus() } }) -const SelectionPopover = GObject.registerClass({ - GTypeName: 'FoliateSelectionPopover', - Template: pkg.moduleuri('ui/selection-popover.ui'), -}, class extends Gtk.PopoverMenu { -}) - const autohide = (revealer, shouldStayVisible) => { const show = () => revealer.reveal_child = true const hide = () => revealer.reveal_child = false @@ -951,7 +946,7 @@ export const BookViewer = GObject.registerClass({ this.#data.storage.set('lastLocation', cfi) } } - #showSelection({ type, value, text, pos: { point, dir } }) { + #showSelection({ type, value, text, lang, pos: { point, dir } }) { if (type === 'annotation') return new Promise(resolve => { const annotation = this.#data.annotations.get(value) const popover = utils.connect(new AnnotationPopover({ annotation }), { @@ -991,10 +986,14 @@ export const BookViewer = GObject.registerClass({ }, 'print': () => resolve('print'), })) - // it seems `closed` is emitted before the actions are run - // so it needs the timeout - popover.connect('closed', () => setTimeout(() => - resolved ? null : resolve(), 0)) + utils.connect(popover, { + 'show-popover': (_, popover) => + this._view.showPopover(popover, point, dir), + 'run-tool': () => ({ text, lang }), + // it seems `closed` is emitted before the actions are run + // so it needs the timeout + 'closed': () => setTimeout(() => resolved ? null : resolve(), 0), + }) this._view.showPopover(popover, point, dir) }) } diff --git a/src/reader/reader.js b/src/reader/reader.js index c5f87de0..0203c34f 100644 --- a/src/reader/reader.js +++ b/src/reader/reader.js @@ -33,6 +33,12 @@ const getSelectionRange = doc => { return range } +const getLang = el => { + const lang = el.lang || el?.getAttributeNS?.('http://www.w3.org/XML/1998/namespace', 'lang') + if (lang) return lang + if (el.parentElement) return getLang(el.parentElement) +} + const blobToBase64 = blob => new Promise(resolve => { const reader = new FileReader() reader.readAsDataURL(blob) @@ -363,7 +369,8 @@ class Reader { if (!range) return const pos = getPosition(range) const value = this.view.getCFI(index, range) - this.#showSelection({ range, value, pos }) + const lang = getLang(range.commonAncestorContainer) + this.#showSelection({ range, lang, value, pos }) }) if (!this.view.isFixedLayout) @@ -386,9 +393,9 @@ class Reader { this.#showSelection({ range, value, pos }) }) } - #showSelection({ range, value, pos }) { + #showSelection({ range, lang, value, pos }) { const text = range.toString() - globalThis.showSelection({ type: 'selection', text, value, pos }) + globalThis.showSelection({ type: 'selection', text, lang, value, pos }) .then(action => { if (action === 'copy') getHTML(range).then(html => emit({ type: 'selection', action, text, html })) diff --git a/src/selection-tools.js b/src/selection-tools.js new file mode 100644 index 00000000..e29b0af4 --- /dev/null +++ b/src/selection-tools.js @@ -0,0 +1,232 @@ +import Gtk from 'gi://Gtk' +import Gio from 'gi://Gio' +import GObject from 'gi://GObject' +import WebKit from 'gi://WebKit' +import Gdk from 'gi://Gdk' +import { gettext as _ } from 'gettext' + +import * as utils from './utils.js' +import { WebView } from './webview.js' + +const tools = { + 'dictionary': { + label: _('Dictionary'), + run: ({ text, lang }) => { + const { language } = new Intl.Locale(lang) + return ` + + +
+ +` + }, + }, +} + +const SelectionToolPopover = GObject.registerClass({ + GTypeName: 'FoliateSelectionToolPopover', +}, class extends Gtk.Popover { + #webView = utils.connect(new WebView({ + settings: new WebKit.Settings({ + enable_write_console_messages_to_stdout: true, + enable_back_forward_navigation_gestures: false, + enable_hyperlink_auditing: false, + enable_html5_database: false, + enable_html5_local_storage: false, + }), + }), { + 'decide-policy': (_, decision, type) => { + switch (type) { + case WebKit.PolicyDecisionType.NAVIGATION_ACTION: + case WebKit.PolicyDecisionType.NEW_WINDOW_ACTION: { + const { uri } = decision.navigation_action.get_request() + if (!uri.startsWith('foliate:')) { + decision.ignore() + Gtk.show_uri(null, uri, Gdk.CURRENT_TIME) + return true + } + } + } + }, + }) + constructor(params) { + super(params) + Object.assign(this, { + width_request: 300, + height_request: 300, + }) + this.child = this.#webView + this.#webView.set_background_color(new Gdk.RGBA()) + } + load(html) { + this.#webView.loadHTML(html, 'foliate:selection-tool') + .then(() => this.#webView.opacity = 1) + .catch(e => console.error(e)) + } +}) + +const getSelectionToolPopover = utils.memoize(() => new SelectionToolPopover()) + +export const SelectionPopover = GObject.registerClass({ + GTypeName: 'FoliateSelectionPopover', + Template: pkg.moduleuri('ui/selection-popover.ui'), + Signals: { + 'show-popover': { param_types: [Gtk.Popover.$gtype] }, + 'run-tool': { return_type: GObject.TYPE_JSOBJECT }, + }, +}, class extends Gtk.PopoverMenu { + constructor(params) { + super(params) + const model = this.menu_model + const section = new Gio.Menu() + model.insert_section(1, null, section) + + const group = new Gio.SimpleActionGroup() + this.insert_action_group('selection-tools', group) + + for (const [name, tool] of Object.entries(tools)) { + const action = new Gio.SimpleAction({ name }) + action.connect('activate', () => { + const popover = getSelectionToolPopover() + Promise.resolve(tool.run(this.emit('run-tool'))) + .then(x => popover.load(x)) + .catch(e => console.error(e)) + this.emit('show-popover', popover) + }) + group.add_action(action) + section.append(tool.label, `selection-tools.${name}`) + } + } +}) diff --git a/src/ui/selection-popover.ui b/src/ui/selection-popover.ui index cc16680d..773f968a 100644 --- a/src/ui/selection-popover.ui +++ b/src/ui/selection-popover.ui @@ -19,20 +19,6 @@ edit-find-symbolic -
- - Dictionary - selection.dictionary - - - Wikipedia - selection.wikipedia - - - Translate - selection.translate - -
Speak