diff --git a/build/configure/src/web.rs b/build/configure/src/web.rs index a37f7c4d3..f77ef2b0e 100644 --- a/build/configure/src/web.rs +++ b/build/configure/src/web.rs @@ -351,7 +351,7 @@ fn build_and_check_pages(build: &mut Build) -> Result<()> { ], )?; build_page( - "import-log", + "import-page", true, inputs![ // diff --git a/ftl/core/importing.ftl b/ftl/core/importing.ftl index 50a89bd17..8763dcc3d 100644 --- a/ftl/core/importing.ftl +++ b/ftl/core/importing.ftl @@ -188,6 +188,36 @@ importing-note-updated-as-file-had-newer = Note updated, as file had newer versi importing-note-skipped-due-to-missing-notetype = Note skipped, as its notetype was missing importing-note-skipped-due-to-missing-deck = Note skipped, as its deck was missing importing-note-skipped-due-to-empty-first-field = Note skipped, as its first field is empty +importing-field-separator-help = + The character separating fields in the text file. You can use the preview to check + if the fields are separated correctly. + + Please note that if this character appears in any field itself, the field has to be + quoted accordingly to the CSV standard. Spreadsheet programs like LibreOffice will + do this automatically. +importing-allow-html-in-fields-help = + Enable this if the file contains HTML formatting. E.g. if the file contains the string + '<br>', it will appear as a line break on your card. On the other hand, with this + option disabled, the literal characters '<br>' will be rendered. +importing-notetype-help = + Newly-imported notes will have this notetype, and only existing notes with this + notetype will be updated. + + You can choose which fields in the file correspond to which notetype fields with the + mapping tool. +importing-deck-help = Imported cards will be placed in this deck. +importing-existing-notes-help = + What to do if an imported note matches an existing one. + + - `{ importing-update }`: Update the existing note. + - `{ importing-preserve }`: Do nothing. + - `{ importing-duplicate }`: Create a new note. +importing-match-scope-help = + Only existing notes with the same notetype will be checked for duplicates. This can + additionally be restricted to notes with cards in the same deck. +importing-tag-all-notes-help = + These tags will be added to both newly-imported and updated notes. +importing-tag-updated-notes-help = These tags will be added to any updated notes. ## NO NEED TO TRANSLATE. This text is no longer used by Anki, and will be removed in the future. diff --git a/qt/aqt/import_export/import_dialog.py b/qt/aqt/import_export/import_dialog.py index e23e24907..2bf0b6e95 100644 --- a/qt/aqt/import_export/import_dialog.py +++ b/qt/aqt/import_export/import_dialog.py @@ -4,6 +4,7 @@ from __future__ import annotations import json +from dataclasses import dataclass import aqt import aqt.deckconf @@ -14,45 +15,76 @@ from aqt.utils import addCloseShortcut, disable_help_button, restoreGeom, saveGe from aqt.webview import AnkiWebView, AnkiWebViewKind +@dataclass +class ImportArgs: + path: str + title = "importLog" + kind = AnkiWebViewKind.IMPORT_LOG + ts_page = "import-page" + setup_function_name = "setupImportPage" + + def args_json(self) -> str: + return json.dumps(self.path) + + +class JsonFileArgs(ImportArgs): + def args_json(self) -> str: + return json.dumps(dict(type="json_file", path=self.path)) + + +@dataclass +class JsonStringArgs(ImportArgs): + json: str + + def args_json(self) -> str: + return json.dumps(dict(type="json_string", path=self.path, json=self.json)) + + +class CsvArgs(ImportArgs): + title = "csv import" + kind = AnkiWebViewKind.IMPORT_CSV + ts_page = "import-csv" + setup_function_name = "setupImportCsvPage" + + +class AnkiPackageArgs(ImportArgs): + title = "anki package import" + kind = AnkiWebViewKind.IMPORT_ANKI_PACKAGE + ts_page = "import-anki-package" + setup_function_name = "setupImportAnkiPackagePage" + + class ImportDialog(QDialog): - TITLE: str - KIND: AnkiWebViewKind - TS_PAGE: str - SETUP_FUNCTION_NAME: str DEFAULT_SIZE = (800, 800) MIN_SIZE = (400, 300) silentlyClose = True - def __init__( - self, - mw: aqt.main.AnkiQt, - path: str, - ) -> None: + def __init__(self, mw: aqt.main.AnkiQt, args: ImportArgs) -> None: QDialog.__init__(self, mw, Qt.WindowType.Window) self.mw = mw - self._setup_ui(path) + self.args = args + self._setup_ui() self.show() - def _setup_ui(self, path: str) -> None: + def _setup_ui(self) -> None: self.setWindowModality(Qt.WindowModality.ApplicationModal) self.mw.garbage_collect_on_dialog_finish(self) self.setMinimumSize(*self.MIN_SIZE) disable_help_button(self) - restoreGeom(self, self.TITLE, default_size=self.DEFAULT_SIZE) + restoreGeom(self, self.args.title, default_size=self.DEFAULT_SIZE) addCloseShortcut(self) - self.web = AnkiWebView(kind=self.KIND) + self.web = AnkiWebView(kind=self.args.kind) self.web.setVisible(False) - self.web.load_ts_page(self.TS_PAGE) + self.web.load_ts_page(self.args.ts_page) layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.web) self.setLayout(layout) - restoreGeom(self, self.TITLE, default_size=(800, 800)) + restoreGeom(self, self.args.title, default_size=(800, 800)) - escaped_path = json.dumps(path.replace("'", r"\'")) self.web.evalWithCallback( - f"anki.{self.SETUP_FUNCTION_NAME}({escaped_path});", + f"anki.{self.args.setup_function_name}({self.args.args_json()});", lambda _: self.web.setFocus(), ) self.setWindowTitle(tr.decks_import_file()) @@ -62,19 +94,5 @@ class ImportDialog(QDialog): self.mw.col.set_wants_abort() self.web.cleanup() self.web = None - saveGeom(self, self.TITLE) + saveGeom(self, self.args.title) QDialog.reject(self) - - -class ImportCsvDialog(ImportDialog): - TITLE = "csv import" - KIND = AnkiWebViewKind.IMPORT_CSV - TS_PAGE = "import-csv" - SETUP_FUNCTION_NAME = "setupImportCsvPage" - - -class ImportAnkiPackageDialog(ImportDialog): - TITLE = "anki package import" - KIND = AnkiWebViewKind.IMPORT_ANKI_PACKAGE - TS_PAGE = "import-anki-package" - SETUP_FUNCTION_NAME = "setupImportAnkiPackagePage" diff --git a/qt/aqt/import_export/import_log_dialog.py b/qt/aqt/import_export/import_log_dialog.py deleted file mode 100644 index f93f1de3b..000000000 --- a/qt/aqt/import_export/import_log_dialog.py +++ /dev/null @@ -1,87 +0,0 @@ -# Copyright: Ankitects Pty Ltd and contributors -# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html - -from __future__ import annotations - -import dataclasses -import json -from dataclasses import dataclass - -import aqt -import aqt.deckconf -import aqt.main -import aqt.operations -from aqt.qt import * -from aqt.utils import addCloseShortcut, disable_help_button, restoreGeom, saveGeom, tr -from aqt.webview import AnkiWebView, AnkiWebViewKind - - -@dataclass -class _CommonArgs: - type: str = dataclasses.field(init=False) - path: str - - def to_json(self) -> str: - return json.dumps(dataclasses.asdict(self)) - - -@dataclass -class JsonFileArgs(_CommonArgs): - type = "json_file" - - -@dataclass -class JsonStringArgs(_CommonArgs): - type = "json_string" - json: str - - -class ImportLogDialog(QDialog): - GEOMETRY_KEY = "importLog" - silentlyClose = True - - def __init__( - self, - mw: aqt.main.AnkiQt, - args: JsonFileArgs | JsonStringArgs, - ) -> None: - QDialog.__init__(self, mw, Qt.WindowType.Window) - self.mw = mw - self._setup_ui(args) - self.show() - - def _setup_ui( - self, - args: JsonFileArgs | JsonStringArgs, - ) -> None: - self.setWindowModality(Qt.WindowModality.ApplicationModal) - self.mw.garbage_collect_on_dialog_finish(self) - self.setMinimumSize(400, 300) - disable_help_button(self) - addCloseShortcut(self) - - self.web = AnkiWebView(kind=AnkiWebViewKind.IMPORT_LOG) - self.web.setVisible(False) - self.web.load_ts_page("import-log") - layout = QVBoxLayout() - layout.setContentsMargins(0, 0, 0, 0) - layout.addWidget(self.web) - self.setLayout(layout) - restoreGeom(self, self.GEOMETRY_KEY, default_size=(800, 800)) - - self.web.evalWithCallback( - "anki.setupImportLogPage(%s);" % (args.to_json()), - lambda _: self.web.setFocus(), - ) - - title = tr.importing_import_log() - title += f" - {os.path.basename(args.path)}" - self.setWindowTitle(title) - - def reject(self) -> None: - if self.mw.col and self.windowModality() == Qt.WindowModality.ApplicationModal: - self.mw.col.set_wants_abort() - self.web.cleanup() - self.web = None - saveGeom(self, self.GEOMETRY_KEY) - QDialog.reject(self) diff --git a/qt/aqt/import_export/importing.py b/qt/aqt/import_export/importing.py index a155630dc..da818dc5c 100644 --- a/qt/aqt/import_export/importing.py +++ b/qt/aqt/import_export/importing.py @@ -13,9 +13,10 @@ from anki.collection import Collection, Progress from anki.errors import Interrupted from anki.foreign_data import mnemosyne from anki.lang import without_unicode_isolation -from aqt.import_export.import_dialog import ImportAnkiPackageDialog, ImportCsvDialog -from aqt.import_export.import_log_dialog import ( - ImportLogDialog, +from aqt.import_export.import_dialog import ( + AnkiPackageArgs, + CsvArgs, + ImportDialog, JsonFileArgs, JsonStringArgs, ) @@ -87,7 +88,7 @@ class ApkgImporter(Importer): @staticmethod def do_import(mw: aqt.main.AnkiQt, path: str) -> None: - ImportAnkiPackageDialog(mw, path) + ImportDialog(mw, AnkiPackageArgs(path)) class MnemosyneImporter(Importer): @@ -98,9 +99,7 @@ class MnemosyneImporter(Importer): QueryOp( parent=mw, op=lambda col: mnemosyne.serialize(path, col.decks.current()["id"]), - success=lambda json: ImportLogDialog( - mw, JsonStringArgs(path=path, json=json) - ), + success=lambda json: ImportDialog(mw, JsonStringArgs(path=path, json=json)), ).with_progress().run_in_background() @@ -109,7 +108,7 @@ class CsvImporter(Importer): @staticmethod def do_import(mw: aqt.main.AnkiQt, path: str) -> None: - ImportCsvDialog(mw, path) + ImportDialog(mw, CsvArgs(path)) class JsonImporter(Importer): @@ -117,7 +116,7 @@ class JsonImporter(Importer): @staticmethod def do_import(mw: aqt.main.AnkiQt, path: str) -> None: - ImportLogDialog(mw, JsonFileArgs(path=path)) + ImportDialog(mw, JsonFileArgs(path=path)) IMPORTERS: list[Type[Importer]] = [ diff --git a/qt/aqt/mediasrv.py b/qt/aqt/mediasrv.py index 33726de77..2ccaf4ac6 100644 --- a/qt/aqt/mediasrv.py +++ b/qt/aqt/mediasrv.py @@ -425,9 +425,8 @@ def import_done() -> bytes: def update_window_modality() -> None: if window := aqt.mw.app.activeWindow(): from aqt.import_export.import_dialog import ImportDialog - from aqt.import_export.import_log_dialog import ImportLogDialog - if isinstance(window, (ImportDialog, ImportLogDialog)): + if isinstance(window, ImportDialog): window.hide() window.setWindowModality(Qt.WindowModality.NonModal) window.show() diff --git a/rslib/io/src/error.rs b/rslib/io/src/error.rs index 1e05047b6..6df493940 100644 --- a/rslib/io/src/error.rs +++ b/rslib/io/src/error.rs @@ -48,7 +48,7 @@ impl FileOp { impl FileIoError { pub fn message(&self) -> String { format!( - "Failed to {} '{}':
{}", + "Failed to {} '{}': {}", match &self.op { FileOp::Unknown => return format!("{}", self.source), FileOp::Open => "open".into(), diff --git a/ts/components/BackendProgressIndicator.svelte b/ts/components/BackendProgressIndicator.svelte index 9a8ebe757..4fe21ce5a 100644 --- a/ts/components/BackendProgressIndicator.svelte +++ b/ts/components/BackendProgressIndicator.svelte @@ -11,7 +11,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html type ResultWithChanges = OpChanges | { changes?: OpChanges }; export let task: () => Promise; - export let result: ResultWithChanges | undefined = undefined; + export let result: ResultWithChanges | undefined; + export let error: Error | undefined; let label: string = ""; function onUpdate(progress: Progress) { @@ -24,8 +25,16 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html } } $: (async () => { - if (!result) { - result = await runWithBackendProgress(task, onUpdate); + if (!result && !error) { + try { + result = await runWithBackendProgress(task, onUpdate); + } catch (err) { + if (err instanceof Error) { + error = err; + } else { + throw err; + } + } } })(); diff --git a/ts/components/EnumSelector.svelte b/ts/components/EnumSelector.svelte index b7e440bc4..21d8039a7 100644 --- a/ts/components/EnumSelector.svelte +++ b/ts/components/EnumSelector.svelte @@ -2,21 +2,34 @@ Copyright: Ankitects Pty Ltd and contributors License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html --> + + - + {#each choices as { label: optionLabel, value: optionValue }} + + {optionLabel} {/each} diff --git a/ts/components/EnumSelectorRow.svelte b/ts/components/EnumSelectorRow.svelte index 58e2403d5..2570b8e65 100644 --- a/ts/components/EnumSelectorRow.svelte +++ b/ts/components/EnumSelectorRow.svelte @@ -5,16 +5,19 @@ @@ -23,7 +26,7 @@ - + diff --git a/ts/components/SwitchRow.svelte b/ts/components/SwitchRow.svelte index dbfbd0895..461dc27e7 100644 --- a/ts/components/SwitchRow.svelte +++ b/ts/components/SwitchRow.svelte @@ -12,6 +12,7 @@ export let value: boolean; export let defaultValue: boolean; + export let disabled: boolean = false; const id = Math.random().toString(36).substring(2); @@ -20,7 +21,7 @@ - + diff --git a/ts/components/TagsRow.svelte b/ts/components/TagsRow.svelte new file mode 100644 index 000000000..0b7256fbd --- /dev/null +++ b/ts/components/TagsRow.svelte @@ -0,0 +1,36 @@ + + + + + + + + + + (tags = detail.tags)} + {keyCombination} + /> + + + + diff --git a/ts/deck-options/DisplayOrder.svelte b/ts/deck-options/DisplayOrder.svelte index 1f6c30d78..5a313c970 100644 --- a/ts/deck-options/DisplayOrder.svelte +++ b/ts/deck-options/DisplayOrder.svelte @@ -19,8 +19,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import SettingTitle from "../components/SettingTitle.svelte"; import TitledContainer from "../components/TitledContainer.svelte"; import type { HelpItem } from "../components/types"; + import { + newGatherPriorityChoices, + newSortOrderChoices, + reviewMixChoices, + reviewOrderChoices, + } from "./choices"; import type { DeckOptionsState } from "./lib"; - import { reviewMixChoices } from "./strings"; export let state: DeckOptionsState; export let api: Record; @@ -30,32 +35,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html const currentDeck = "\n\n" + tr.deckConfigDisplayOrderWillUseCurrentDeck(); - const newGatherPriorityChoices = [ - tr.deckConfigNewGatherPriorityDeck(), - tr.deckConfigNewGatherPriorityPositionLowestFirst(), - tr.deckConfigNewGatherPriorityPositionHighestFirst(), - tr.deckConfigNewGatherPriorityRandomNotes(), - tr.deckConfigNewGatherPriorityRandomCards(), - ]; - const newSortOrderChoices = [ - tr.deckConfigSortOrderTemplateThenGather(), - tr.deckConfigSortOrderGather(), - tr.deckConfigSortOrderCardTemplateThenRandom(), - tr.deckConfigSortOrderRandomNoteThenTemplate(), - tr.deckConfigSortOrderRandom(), - ]; - const reviewOrderChoices = [ - tr.deckConfigSortOrderDueDateThenRandom(), - tr.deckConfigSortOrderDueDateThenDeck(), - tr.deckConfigSortOrderDeckThenDueDate(), - tr.deckConfigSortOrderAscendingIntervals(), - tr.deckConfigSortOrderDescendingIntervals(), - tr.deckConfigSortOrderAscendingEase(), - tr.deckConfigSortOrderDescendingEase(), - tr.deckConfigSortOrderRelativeOverdueness(), - tr.deckConfigSortOrderRandom(), - ]; - let disabledNewSortOrders: number[] = []; $: { switch ($config.newCardGatherPriority) { @@ -143,7 +122,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html @@ -160,8 +139,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html @@ -212,7 +191,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html diff --git a/ts/deck-options/LapseOptions.svelte b/ts/deck-options/LapseOptions.svelte index 75b0293c1..de4ca035f 100644 --- a/ts/deck-options/LapseOptions.svelte +++ b/ts/deck-options/LapseOptions.svelte @@ -15,6 +15,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import SettingTitle from "../components/SettingTitle.svelte"; import TitledContainer from "../components/TitledContainer.svelte"; import type { HelpItem } from "../components/types"; + import { leechChoices } from "./choices"; import type { DeckOptionsState } from "./lib"; import SpinBoxRow from "./SpinBoxRow.svelte"; import StepsInputRow from "./StepsInputRow.svelte"; @@ -37,8 +38,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html : ""; } - const leechChoices = [tr.actionsSuspendCard(), tr.schedulingTagOnly()]; - const settings = { relearningSteps: { title: tr.deckConfigRelearningSteps(), @@ -136,7 +135,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html [] { + return [ + { + label: tr.deckConfigNewGatherPriorityDeck(), + value: DeckConfig_Config_NewCardGatherPriority.DECK, + }, + { + label: tr.deckConfigNewGatherPriorityPositionLowestFirst(), + value: DeckConfig_Config_NewCardGatherPriority.LOWEST_POSITION, + }, + { + label: tr.deckConfigNewGatherPriorityPositionHighestFirst(), + value: DeckConfig_Config_NewCardGatherPriority.HIGHEST_POSITION, + }, + { + label: tr.deckConfigNewGatherPriorityRandomNotes(), + value: DeckConfig_Config_NewCardGatherPriority.RANDOM_NOTES, + }, + { + label: tr.deckConfigNewGatherPriorityRandomCards(), + value: DeckConfig_Config_NewCardGatherPriority.RANDOM_CARDS, + }, + ]; +} + +export function newSortOrderChoices(): Choice[] { + return [ + { + label: tr.deckConfigSortOrderTemplateThenGather(), + value: DeckConfig_Config_NewCardSortOrder.TEMPLATE, + }, + { + label: tr.deckConfigSortOrderGather(), + value: DeckConfig_Config_NewCardSortOrder.NO_SORT, + }, + { + label: tr.deckConfigSortOrderCardTemplateThenRandom(), + value: DeckConfig_Config_NewCardSortOrder.TEMPLATE_THEN_RANDOM, + }, + { + label: tr.deckConfigSortOrderRandomNoteThenTemplate(), + value: DeckConfig_Config_NewCardSortOrder.RANDOM_NOTE_THEN_TEMPLATE, + }, + { + label: tr.deckConfigSortOrderRandom(), + value: DeckConfig_Config_NewCardSortOrder.RANDOM_CARD, + }, + ]; +} + +export function reviewOrderChoices(): Choice[] { + return [ + { + label: tr.deckConfigSortOrderDueDateThenRandom(), + value: DeckConfig_Config_ReviewCardOrder.DAY, + }, + { + label: tr.deckConfigSortOrderDueDateThenDeck(), + value: DeckConfig_Config_ReviewCardOrder.DAY_THEN_DECK, + }, + { + label: tr.deckConfigSortOrderDeckThenDueDate(), + value: DeckConfig_Config_ReviewCardOrder.DECK_THEN_DAY, + }, + { + label: tr.deckConfigSortOrderAscendingIntervals(), + value: DeckConfig_Config_ReviewCardOrder.INTERVALS_ASCENDING, + }, + { + label: tr.deckConfigSortOrderDescendingIntervals(), + value: DeckConfig_Config_ReviewCardOrder.INTERVALS_DESCENDING, + }, + { + label: tr.deckConfigSortOrderAscendingEase(), + value: DeckConfig_Config_ReviewCardOrder.EASE_ASCENDING, + }, + { + label: tr.deckConfigSortOrderDescendingEase(), + value: DeckConfig_Config_ReviewCardOrder.EASE_DESCENDING, + }, + { + label: tr.deckConfigSortOrderRelativeOverdueness(), + value: DeckConfig_Config_ReviewCardOrder.RELATIVE_OVERDUENESS, + }, + { + label: tr.deckConfigSortOrderRandom(), + value: DeckConfig_Config_ReviewCardOrder.RANDOM, + }, + ]; +} + +export function reviewMixChoices(): Choice[] { + return [ + { + label: tr.deckConfigReviewMixMixWithReviews(), + value: DeckConfig_Config_ReviewMix.MIX_WITH_REVIEWS, + }, + { + label: tr.deckConfigReviewMixShowAfterReviews(), + value: DeckConfig_Config_ReviewMix.AFTER_REVIEWS, + }, + { + label: tr.deckConfigReviewMixShowBeforeReviews(), + value: DeckConfig_Config_ReviewMix.BEFORE_REVIEWS, + }, + ]; +} + +export function leechChoices(): Choice[] { + return [ + { + label: tr.actionsSuspendCard(), + value: DeckConfig_Config_LeechAction.SUSPEND, + }, + { + label: tr.schedulingTagOnly(), + value: DeckConfig_Config_LeechAction.TAG_ONLY, + }, + ]; +} + +export function newInsertOrderChoices(): Choice[] { + return [ + { + label: tr.deckConfigNewInsertionOrderSequential(), + value: DeckConfig_Config_NewCardInsertOrder.DUE, + }, + { + label: tr.deckConfigNewInsertionOrderRandom(), + value: DeckConfig_Config_NewCardInsertOrder.RANDOM, + }, + ]; +} diff --git a/ts/deck-options/strings.ts b/ts/deck-options/strings.ts deleted file mode 100644 index 33c434971..000000000 --- a/ts/deck-options/strings.ts +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright: Ankitects Pty Ltd and contributors -// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html - -import * as tr from "@tslib/ftl"; - -export const reviewMixChoices = (): string[] => [ - tr.deckConfigReviewMixMixWithReviews(), - tr.deckConfigReviewMixShowAfterReviews(), - tr.deckConfigReviewMixShowBeforeReviews(), -]; diff --git a/ts/import-anki-package/ImportAnkiPackagePage.svelte b/ts/import-anki-package/ImportAnkiPackagePage.svelte index c08d26009..71fe88aff 100644 --- a/ts/import-anki-package/ImportAnkiPackagePage.svelte +++ b/ts/import-anki-package/ImportAnkiPackagePage.svelte @@ -3,41 +3,26 @@ Copyright: Ankitects Pty Ltd and contributors License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html --> -{#if importing} - -{:else if importResponse} - -{:else} - (importing = true)} /> + + importAnkiPackage({ packagePath: path, options }, { alertOnError: false }), + }} +> + + + { + modal = e.detail.modal; + carousel = e.detail.carousel; + }} + /> - - - - { - modal = e.detail.modal; - carousel = e.detail.carousel; - }} - /> - - - - openHelpModal( - Object.keys(settings).indexOf("mergeNotetypes"), - )} - > - {settings.mergeNotetypes.title} - - - - + + openHelpModal(Object.keys(settings).indexOf("mergeNotetypes"))} > - - openHelpModal(Object.keys(settings).indexOf("updateNotes"))} - > - {settings.updateNotes.title} - - + {settings.mergeNotetypes.title} + + - + + openHelpModal(Object.keys(settings).indexOf("updateNotes"))} > - - openHelpModal( - Object.keys(settings).indexOf("updateNotetypes"), - )} - > - {settings.updateNotetypes.title} - - + {settings.updateNotes.title} + + - - - openHelpModal( - Object.keys(settings).indexOf("withScheduling"), - )} - > - {settings.withScheduling.title} - - - - - -{/if} + + + openHelpModal(Object.keys(settings).indexOf("updateNotetypes"))} + > + {settings.updateNotetypes.title} + + - + + + openHelpModal(Object.keys(settings).indexOf("withScheduling"))} + > + {settings.withScheduling.title} + + + + + diff --git a/ts/import-anki-package/choices.ts b/ts/import-anki-package/choices.ts new file mode 100644 index 000000000..49a456e62 --- /dev/null +++ b/ts/import-anki-package/choices.ts @@ -0,0 +1,23 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +import { ImportAnkiPackageUpdateCondition } from "@tslib/anki/import_export_pb"; +import * as tr from "@tslib/ftl"; +import type { Choice } from "components/EnumSelector.svelte"; + +export function updateChoices(): Choice[] { + return [ + { + label: tr.importingUpdateIfNewer(), + value: ImportAnkiPackageUpdateCondition.IF_NEWER, + }, + { + label: tr.importingUpdateAlways(), + value: ImportAnkiPackageUpdateCondition.ALWAYS, + }, + { + label: tr.importingUpdateNever(), + value: ImportAnkiPackageUpdateCondition.NEVER, + }, + ]; +} diff --git a/ts/import-anki-package/import-anki-package-base.scss b/ts/import-anki-package/import-anki-package-base.scss index efcc43878..91c0fc932 100644 --- a/ts/import-anki-package/import-anki-package-base.scss +++ b/ts/import-anki-package/import-anki-package-base.scss @@ -18,9 +18,6 @@ } body { - min-height: 100vh; - width: min(100vw, 70em); - margin: 0 auto; padding: 0 1em 1em 1em; } diff --git a/ts/import-csv/DeckDupeCheckSwitch.svelte b/ts/import-csv/DeckDupeCheckSwitch.svelte deleted file mode 100644 index 4c4629091..000000000 --- a/ts/import-csv/DeckDupeCheckSwitch.svelte +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - {tr.importingMatchScope()} - - - - - diff --git a/ts/import-csv/DeckSelector.svelte b/ts/import-csv/DeckSelector.svelte deleted file mode 100644 index a9d4e00dc..000000000 --- a/ts/import-csv/DeckSelector.svelte +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - {tr.decksDeck()} - - - - - diff --git a/ts/import-csv/DelimiterSelector.svelte b/ts/import-csv/DelimiterSelector.svelte deleted file mode 100644 index 6e0b707cb..000000000 --- a/ts/import-csv/DelimiterSelector.svelte +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - {tr.importingFieldSeparator()} - - - - - diff --git a/ts/import-csv/DupeResolutionSelector.svelte b/ts/import-csv/DupeResolutionSelector.svelte deleted file mode 100644 index aee37613a..000000000 --- a/ts/import-csv/DupeResolutionSelector.svelte +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - {tr.importingExistingNotes()} - - - - - diff --git a/ts/import-csv/FieldMapper.svelte b/ts/import-csv/FieldMapper.svelte index 42362ccce..5c27a47bf 100644 --- a/ts/import-csv/FieldMapper.svelte +++ b/ts/import-csv/FieldMapper.svelte @@ -3,41 +3,36 @@ Copyright: Ankitects Pty Ltd and contributors License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html --> -{#if globalNotetype} - {#await fieldNamesPromise then fieldNames} - {#each fieldNames as label, idx} - - - {/each} - {/await} - -{/if} - + + {#if $globalNotetype !== null} + {#await $fieldNamesPromise then fieldNames} + {#each fieldNames as label, idx} + + + {/each} + {/await} + {/if} + + diff --git a/ts/import-csv/FileOptions.svelte b/ts/import-csv/FileOptions.svelte new file mode 100644 index 000000000..54fb70704 --- /dev/null +++ b/ts/import-csv/FileOptions.svelte @@ -0,0 +1,84 @@ + + + + + { + modal = e.detail.modal; + carousel = e.detail.carousel; + }} + /> + + openHelpModal(Object.keys(settings).indexOf("delimiter"))} + > + {settings.delimiter.title} + + + + + openHelpModal(Object.keys(settings).indexOf("isHtml"))} + > + {settings.isHtml.title} + + + + + diff --git a/ts/import-csv/Header.svelte b/ts/import-csv/Header.svelte deleted file mode 100644 index ca5b93261..000000000 --- a/ts/import-csv/Header.svelte +++ /dev/null @@ -1,17 +0,0 @@ - - - -

- {heading} -

- - diff --git a/ts/import-csv/HtmlSwitch.svelte b/ts/import-csv/HtmlSwitch.svelte deleted file mode 100644 index dd00f0783..000000000 --- a/ts/import-csv/HtmlSwitch.svelte +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - {tr.importingAllowHtmlInFields()} - - - - - diff --git a/ts/import-csv/ImportCsvPage.svelte b/ts/import-csv/ImportCsvPage.svelte index 76e035681..9c64f4af5 100644 --- a/ts/import-csv/ImportCsvPage.svelte +++ b/ts/import-csv/ImportCsvPage.svelte @@ -3,192 +3,18 @@ Copyright: Ankitects Pty Ltd and contributors License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html --> -
- {#if importing} - - {:else if importResponse} - - {:else} - (importing = true)} /> - - - - - -
- - - - - - - - -
- - {#if globalNotetype} - - {/if} - {#if deckId} - - {/if} - - - - - - - -
- - - - - - - {/if} -
- - + + + + + diff --git a/ts/import-csv/ImportOptions.svelte b/ts/import-csv/ImportOptions.svelte new file mode 100644 index 000000000..5a1f93764 --- /dev/null +++ b/ts/import-csv/ImportOptions.svelte @@ -0,0 +1,153 @@ + + + + + { + modal = e.detail.modal; + carousel = e.detail.carousel; + }} + /> + + {#if $globalNotetype !== null} + { + return { label: name, value: id }; + })} + > + + openHelpModal(Object.keys(settings).indexOf("notetype"))} + > + {settings.notetype.title} + + + {/if} + + {#if $deckId !== null} + { + return { label: name, value: id }; + })} + > + openHelpModal(Object.keys(settings).indexOf("deck"))} + > + {settings.deck.title} + + + {/if} + + + + openHelpModal(Object.keys(settings).indexOf("dupeResolution"))} + > + {settings.dupeResolution.title} + + + + + openHelpModal(Object.keys(settings).indexOf("matchScope"))} + > + {settings.matchScope.title} + + + + + openHelpModal(Object.keys(settings).indexOf("globalTags"))} + > + {settings.globalTags.title} + + + + + openHelpModal(Object.keys(settings).indexOf("updatedTags"))} + > + {settings.updatedTags.title} + + + diff --git a/ts/import-csv/NotetypeSelector.svelte b/ts/import-csv/NotetypeSelector.svelte deleted file mode 100644 index bde285ecb..000000000 --- a/ts/import-csv/NotetypeSelector.svelte +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - {tr.notetypesNotetype()} - - - - - diff --git a/ts/import-csv/Preview.svelte b/ts/import-csv/Preview.svelte index 4f9232f1e..d79223019 100644 --- a/ts/import-csv/Preview.svelte +++ b/ts/import-csv/Preview.svelte @@ -3,22 +3,22 @@ Copyright: Ankitects Pty Ltd and contributors License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -->
- {#each columnOptions.slice(1) as { label, shortLabel }} + {#each $columnOptions.slice(1) as { label, shortLabel }} {/each} - {#each preview as row} + {#each $metadata.preview as row} {#each row.vals as cell} @@ -30,8 +30,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html diff --git a/ts/import-log/QueueSummary.svelte b/ts/import-page/QueueSummary.svelte similarity index 100% rename from ts/import-log/QueueSummary.svelte rename to ts/import-page/QueueSummary.svelte diff --git a/ts/import-log/TableCell.svelte b/ts/import-page/TableCell.svelte similarity index 100% rename from ts/import-log/TableCell.svelte rename to ts/import-page/TableCell.svelte diff --git a/ts/import-log/TableCellWithTooltip.svelte b/ts/import-page/TableCellWithTooltip.svelte similarity index 100% rename from ts/import-log/TableCellWithTooltip.svelte rename to ts/import-page/TableCellWithTooltip.svelte diff --git a/ts/import-log/icons.ts b/ts/import-page/icons.ts similarity index 100% rename from ts/import-log/icons.ts rename to ts/import-page/icons.ts diff --git a/ts/import-log/import-log-base.scss b/ts/import-page/import-page-base.scss similarity index 100% rename from ts/import-log/import-log-base.scss rename to ts/import-page/import-page-base.scss diff --git a/ts/import-page/index.ts b/ts/import-page/index.ts new file mode 100644 index 000000000..9bbec1620 --- /dev/null +++ b/ts/import-page/index.ts @@ -0,0 +1,54 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +import "./import-page-base.scss"; + +import { importJsonFile, importJsonString } from "@tslib/backend"; +import { ModuleName, setupI18n } from "@tslib/i18n"; +import { checkNightMode } from "@tslib/nightmode"; + +import ImportPage from "./ImportPage.svelte"; +import type { LogParams } from "./types"; + +const i18n = setupI18n({ + modules: [ + ModuleName.IMPORTING, + ModuleName.ADDING, + ModuleName.EDITING, + ModuleName.ACTIONS, + ModuleName.KEYBOARD, + ], +}); + +const postOptions = { alertOnError: false }; + +export async function setupImportPage( + params: LogParams, +): Promise { + await i18n; + + checkNightMode(); + + return new ImportPage({ + target: document.body, + props: { + path: params.path, + noOptions: true, + importer: { + doImport: () => { + switch (params.type) { + case "json_file": + return importJsonFile({ val: params.path }, postOptions); + case "json_string": + return importJsonString({ val: params.json }, postOptions); + } + }, + }, + }, + }); +} + +if (window.location.hash.startsWith("#test-")) { + const path = window.location.hash.replace("#test-", ""); + setupImportPage({ type: "json_file", path }); +} diff --git a/ts/import-log/lib.ts b/ts/import-page/lib.ts similarity index 100% rename from ts/import-log/lib.ts rename to ts/import-page/lib.ts diff --git a/ts/import-log/tsconfig.json b/ts/import-page/tsconfig.json similarity index 100% rename from ts/import-log/tsconfig.json rename to ts/import-page/tsconfig.json diff --git a/ts/import-log/types.ts b/ts/import-page/types.ts similarity index 92% rename from ts/import-log/types.ts rename to ts/import-page/types.ts index 2e74d9b85..609124dc8 100644 --- a/ts/import-log/types.ts +++ b/ts/import-page/types.ts @@ -23,12 +23,12 @@ export type NoteRow = { }; type PathParams = { - type?: "apkg" | "json_file"; + type: "json_file"; path: string; }; type JsonParams = { - type?: "json_string"; + type: "json_string"; path: string; json: string; }; diff --git a/ts/lib/help-page.ts b/ts/lib/help-page.ts index 97c23a830..c7ec8173c 100644 --- a/ts/lib/help-page.ts +++ b/ts/lib/help-page.ts @@ -38,4 +38,9 @@ export const HelpPage = { updating: "https://docs.ankiweb.net/importing/packaged-decks.html#updating", scheduling: "https://docs.ankiweb.net/importing/packaged-decks.html#scheduling", }, + TextImporting: { + root: "https://docs.ankiweb.net/importing/text-files.html", + updating: "https://docs.ankiweb.net/importing/text-files.html#duplicates-and-updating", + html: "https://docs.ankiweb.net/importing/text-files.html#html", + }, };
{shortLabel || label}
{cell}