mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00
Add deck/collection export hooks (#1971)
* Add ExportFormat enum and use it in Exporter classes
* Add exporter hooks and call them from new exporters
* Fix filter argument order and add example to docstring
* Refactor: Avoid repeating ExportFormat
* Rename Options to ExportOptions for better namespacing in add-ons
* Add simplified legacy exporter hooks
Allows add-ons to be notified of exports when legacy handlers are enabled, without the need for monkey-patches.
* Switch away from ExportFormat, opting to pass exporter class/instance instead
* Consistently use exporter instances rather than classes
* Revert Exportdialog.exporters rename
* Revert "Revert Exportdialog.exporters rename"
This reverts commit 357a3aa859
.
This commit is contained in:
parent
b9fd6688d2
commit
070c8ac735
3 changed files with 78 additions and 26 deletions
|
@ -196,6 +196,7 @@ class ExportDialog(QDialog):
|
|||
else:
|
||||
self.on_export_finished()
|
||||
|
||||
gui_hooks.legacy_exporter_will_export(self.exporter)
|
||||
if self.isVerbatim:
|
||||
gui_hooks.collection_will_temporarily_close(self.mw.col)
|
||||
self.mw.progress.start()
|
||||
|
@ -213,6 +214,7 @@ class ExportDialog(QDialog):
|
|||
msg = tr.exporting_note_exported(count=self.exporter.count)
|
||||
else:
|
||||
msg = tr.exporting_card_exported(count=self.exporter.count)
|
||||
gui_hooks.legacy_exporter_did_export(self.exporter)
|
||||
tooltip(msg, period=3000)
|
||||
QDialog.reject(self)
|
||||
|
||||
|
|
|
@ -42,21 +42,21 @@ class ExportDialog(QDialog):
|
|||
self.col = mw.col.weakref()
|
||||
self.frm = aqt.forms.exporting.Ui_ExportDialog()
|
||||
self.frm.setupUi(self)
|
||||
self.exporter: Type[Exporter] = None
|
||||
self.exporter: Exporter
|
||||
self.nids = nids
|
||||
disable_help_button(self)
|
||||
self.setup(did)
|
||||
self.open()
|
||||
|
||||
def setup(self, did: DeckId | None) -> None:
|
||||
self.exporters: list[Type[Exporter]] = [
|
||||
self.exporter_classes: list[Type[Exporter]] = [
|
||||
ApkgExporter,
|
||||
ColpkgExporter,
|
||||
NoteCsvExporter,
|
||||
CardCsvExporter,
|
||||
]
|
||||
self.frm.format.insertItems(
|
||||
0, [f"{e.name()} (.{e.extension})" for e in self.exporters]
|
||||
0, [f"{e.name()} (.{e.extension})" for e in self.exporter_classes]
|
||||
)
|
||||
qconnect(self.frm.format.activated, self.exporter_changed)
|
||||
if self.nids is None and not did:
|
||||
|
@ -86,7 +86,7 @@ class ExportDialog(QDialog):
|
|||
self.frm.includeSched.setChecked(False)
|
||||
|
||||
def exporter_changed(self, idx: int) -> None:
|
||||
self.exporter = self.exporters[idx]
|
||||
self.exporter = self.exporter_classes[idx]()
|
||||
self.frm.includeSched.setVisible(self.exporter.show_include_scheduling)
|
||||
self.frm.includeMedia.setVisible(self.exporter.show_include_media)
|
||||
self.frm.includeTags.setVisible(self.exporter.show_include_tags)
|
||||
|
@ -125,14 +125,14 @@ class ExportDialog(QDialog):
|
|||
break
|
||||
return path
|
||||
|
||||
def options(self, out_path: str) -> Options:
|
||||
def options(self, out_path: str) -> ExportOptions:
|
||||
limit: ExportLimit = None
|
||||
if self.nids:
|
||||
limit = NoteIdsLimit(self.nids)
|
||||
elif current_deck_id := self.current_deck_id():
|
||||
limit = DeckIdLimit(current_deck_id)
|
||||
|
||||
return Options(
|
||||
return ExportOptions(
|
||||
out_path=out_path,
|
||||
include_scheduling=self.frm.includeSched.isChecked(),
|
||||
include_media=self.frm.includeMedia.isChecked(),
|
||||
|
@ -165,7 +165,7 @@ class ExportDialog(QDialog):
|
|||
|
||||
|
||||
@dataclass
|
||||
class Options:
|
||||
class ExportOptions:
|
||||
out_path: str
|
||||
include_scheduling: bool
|
||||
include_media: bool
|
||||
|
@ -190,9 +190,8 @@ class Exporter(ABC):
|
|||
show_include_notetype = False
|
||||
show_include_guid = False
|
||||
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def export(mw: aqt.main.AnkiQt, options: Options) -> None:
|
||||
def export(self, mw: aqt.main.AnkiQt, options: ExportOptions) -> None:
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
|
@ -210,10 +209,12 @@ class ColpkgExporter(Exporter):
|
|||
def name() -> str:
|
||||
return tr.exporting_anki_collection_package()
|
||||
|
||||
@staticmethod
|
||||
def export(mw: aqt.main.AnkiQt, options: Options) -> None:
|
||||
def export(self, mw: aqt.main.AnkiQt, options: ExportOptions) -> None:
|
||||
options = gui_hooks.exporter_will_export(options, self)
|
||||
|
||||
def on_success(_: None) -> None:
|
||||
mw.reopen()
|
||||
gui_hooks.exporter_did_export(options, self)
|
||||
tooltip(tr.exporting_collection_exported(), parent=mw)
|
||||
|
||||
def on_failure(exception: Exception) -> None:
|
||||
|
@ -245,8 +246,13 @@ class ApkgExporter(Exporter):
|
|||
def name() -> str:
|
||||
return tr.exporting_anki_deck_package()
|
||||
|
||||
@staticmethod
|
||||
def export(mw: aqt.main.AnkiQt, options: Options) -> None:
|
||||
def export(self, mw: aqt.main.AnkiQt, options: ExportOptions) -> None:
|
||||
options = gui_hooks.exporter_will_export(options, self)
|
||||
|
||||
def on_success(count: int) -> None:
|
||||
gui_hooks.exporter_did_export(options, self)
|
||||
tooltip(tr.exporting_note_exported(count=count), parent=mw)
|
||||
|
||||
QueryOp(
|
||||
parent=mw,
|
||||
op=lambda col: col.export_anki_package(
|
||||
|
@ -256,9 +262,7 @@ class ApkgExporter(Exporter):
|
|||
with_media=options.include_media,
|
||||
legacy_support=options.legacy_support,
|
||||
),
|
||||
success=lambda count: tooltip(
|
||||
tr.exporting_note_exported(count=count), parent=mw
|
||||
),
|
||||
success=on_success,
|
||||
).with_backend_progress(export_progress_update).run_in_background()
|
||||
|
||||
|
||||
|
@ -275,8 +279,13 @@ class NoteCsvExporter(Exporter):
|
|||
def name() -> str:
|
||||
return tr.exporting_notes_in_plain_text()
|
||||
|
||||
@staticmethod
|
||||
def export(mw: aqt.main.AnkiQt, options: Options) -> None:
|
||||
def export(self, mw: aqt.main.AnkiQt, options: ExportOptions) -> None:
|
||||
options = gui_hooks.exporter_will_export(options, self)
|
||||
|
||||
def on_success(count: int) -> None:
|
||||
gui_hooks.exporter_did_export(options, self)
|
||||
tooltip(tr.exporting_note_exported(count=count), parent=mw)
|
||||
|
||||
QueryOp(
|
||||
parent=mw,
|
||||
op=lambda col: col.export_note_csv(
|
||||
|
@ -288,9 +297,7 @@ class NoteCsvExporter(Exporter):
|
|||
with_notetype=options.include_notetype,
|
||||
with_guid=options.include_guid,
|
||||
),
|
||||
success=lambda count: tooltip(
|
||||
tr.exporting_note_exported(count=count), parent=mw
|
||||
),
|
||||
success=on_success,
|
||||
).with_backend_progress(export_progress_update).run_in_background()
|
||||
|
||||
|
||||
|
@ -303,8 +310,13 @@ class CardCsvExporter(Exporter):
|
|||
def name() -> str:
|
||||
return tr.exporting_cards_in_plain_text()
|
||||
|
||||
@staticmethod
|
||||
def export(mw: aqt.main.AnkiQt, options: Options) -> None:
|
||||
def export(self, mw: aqt.main.AnkiQt, options: ExportOptions) -> None:
|
||||
options = gui_hooks.exporter_will_export(options, self)
|
||||
|
||||
def on_success(count: int) -> None:
|
||||
gui_hooks.exporter_did_export(options, self)
|
||||
tooltip(tr.exporting_card_exported(count=count), parent=mw)
|
||||
|
||||
QueryOp(
|
||||
parent=mw,
|
||||
op=lambda col: col.export_card_csv(
|
||||
|
@ -312,9 +324,7 @@ class CardCsvExporter(Exporter):
|
|||
limit=options.limit,
|
||||
with_html=options.include_html,
|
||||
),
|
||||
success=lambda count: tooltip(
|
||||
tr.exporting_card_exported(count=count), parent=mw
|
||||
),
|
||||
success=on_success,
|
||||
).with_backend_progress(export_progress_update).run_in_background()
|
||||
|
||||
|
||||
|
|
|
@ -816,6 +816,46 @@ gui_hooks.webview_did_inject_style_into_page.append(mytest)
|
|||
|
||||
`output` provides access to the unused/missing file lists and the text output that will be shown in the Check Media screen.""",
|
||||
),
|
||||
# Importing/exporting data
|
||||
###################
|
||||
Hook(
|
||||
name="exporter_will_export",
|
||||
args=[
|
||||
"export_options: aqt.import_export.exporting.ExportOptions",
|
||||
"exporter: aqt.import_export.exporting.Exporter",
|
||||
],
|
||||
return_type="aqt.import_export.exporting.ExportOptions",
|
||||
doc="""Called before collection and deck exports.
|
||||
|
||||
Allows add-ons to be notified of impending deck exports and potentially
|
||||
modify the export options. To perform the export unaltered, please return
|
||||
`export_options` as is, e.g.:
|
||||
|
||||
def on_exporter_will_export(export_options: ExportOptions, exporter: Exporter):
|
||||
if not isinstance(exporter, ApkgExporter):
|
||||
return export_options
|
||||
export_options.limit = ...
|
||||
return export_options
|
||||
""",
|
||||
),
|
||||
Hook(
|
||||
name="exporter_did_export",
|
||||
args=[
|
||||
"export_options: aqt.import_export.exporting.ExportOptions",
|
||||
"exporter: aqt.import_export.exporting.Exporter",
|
||||
],
|
||||
doc="""Called after collection and deck exports.""",
|
||||
),
|
||||
Hook(
|
||||
name="legacy_exporter_will_export",
|
||||
args=["legacy_exporter: anki.exporting.Exporter"],
|
||||
doc="""Called before collection and deck exports performed by legacy exporters.""",
|
||||
),
|
||||
Hook(
|
||||
name="legacy_exporter_did_export",
|
||||
args=["legacy_exporter: anki.exporting.Exporter"],
|
||||
doc="""Called after collection and deck exports performed by legacy exporters.""",
|
||||
),
|
||||
# Dialog Manager
|
||||
###################
|
||||
Hook(
|
||||
|
|
Loading…
Reference in a new issue