mirror of
https://github.com/ankitects/anki.git
synced 2025-09-21 07:22:23 -04:00

* Remember original id when importing notetype * Reuse notetypes with matching original id * Add field and template ids * Enable merging imported notetypes * Fix test Note should be updated if the incoming note's notetype is remapped to the existing note's notetype. On the other hand, it should be skipped if its notetype id is mapped to some new notetype. * Change field and template ids to i32 * Add merge notetypes flag to proto message * Add dialog for apkg import * Move HelpModal into components * Generalize import dialog * Move SettingTitle into components * Add help modal to ImportAnkiPackagePage * Move SwitchRow into components * Fix backend method import * Make testable in browser * Fix broken modal * Wrap in container and fix margins * Update commented Anki version of new proto fields * Check ids when comparing notetype schemas * Add tooltip for merging notetypes. * Allow updating notes regardless of mtime * Gitignore yarn-error.log * Allow updating notetypes regardless of mtime * Fix apkg help carousel * Use i64s for template and field ids * Add option to omit importing scheduling info * Restore last settings in apkg import dialog * Display error when getting metadata in webview * Update manual links for apkg importing * Apply suggestions from code review Co-authored-by: Damien Elmes <dae@users.noreply.github.com> * Omit schduling -> Import all cards as new cards * Tweak importing-update-notes-help * UpdateCondition → ImportAnkiPackageUpdateCondition * Load keyboard.ftl * Skip updating dupes in 'update alwyas' case * Explain more when merging notetypes is required * "omit scheduling" → "with scheduling" * Skip updating notetype dupes if 'update always' * Merge duplicated notetypes from previous imports * Fix rebase aftermath * Fix panic when merging * Clarify 'update notetypes' help * Mention 'merge notetypes' in the log * Add a test which covers the previously panicking path * Use nested ftl messages to ensure consistency * Make order of merged fields deterministic * Rewrite test to trigger panic * Update version comment on new fields
194 lines
6.9 KiB
Svelte
194 lines
6.9 KiB
Svelte
<!--
|
|
Copyright: Ankitects Pty Ltd and contributors
|
|
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|
-->
|
|
<script lang="ts">
|
|
import type { DeckNameId } from "@tslib/anki/decks_pb";
|
|
import type { StringList } from "@tslib/anki/generic_pb";
|
|
import type {
|
|
CsvMetadata_Delimiter,
|
|
CsvMetadata_DupeResolution,
|
|
CsvMetadata_MappedNotetype,
|
|
CsvMetadata_MatchScope,
|
|
ImportResponse,
|
|
} from "@tslib/anki/import_export_pb";
|
|
import type { NotetypeNameId } from "@tslib/anki/notetypes_pb";
|
|
import { getCsvMetadata, importCsv, importDone } from "@tslib/backend";
|
|
import * as tr from "@tslib/ftl";
|
|
|
|
import BackendProgressIndicator from "../components/BackendProgressIndicator.svelte";
|
|
import Col from "../components/Col.svelte";
|
|
import Container from "../components/Container.svelte";
|
|
import Row from "../components/Row.svelte";
|
|
import Spacer from "../components/Spacer.svelte";
|
|
import StickyHeader from "../components/StickyHeader.svelte";
|
|
import ImportLogPage from "../import-log/ImportLogPage.svelte";
|
|
import DeckDupeCheckSwitch from "./DeckDupeCheckSwitch.svelte";
|
|
import DeckSelector from "./DeckSelector.svelte";
|
|
import DelimiterSelector from "./DelimiterSelector.svelte";
|
|
import DupeResolutionSelector from "./DupeResolutionSelector.svelte";
|
|
import FieldMapper from "./FieldMapper.svelte";
|
|
import Header from "./Header.svelte";
|
|
import HtmlSwitch from "./HtmlSwitch.svelte";
|
|
import {
|
|
buildDeckOneof,
|
|
buildNotetypeOneof,
|
|
getColumnOptions,
|
|
tryGetDeckId,
|
|
tryGetGlobalNotetype,
|
|
} from "./lib";
|
|
import NotetypeSelector from "./NotetypeSelector.svelte";
|
|
import Preview from "./Preview.svelte";
|
|
import Tags from "./Tags.svelte";
|
|
|
|
export let path: string;
|
|
export let notetypeNameIds: NotetypeNameId[];
|
|
export let deckNameIds: DeckNameId[];
|
|
export let dupeResolution: CsvMetadata_DupeResolution;
|
|
export let matchScope: CsvMetadata_MatchScope;
|
|
export let delimiter: CsvMetadata_Delimiter;
|
|
export let forceDelimiter: boolean;
|
|
export let forceIsHtml: boolean;
|
|
export let isHtml: boolean;
|
|
export let globalTags: string[];
|
|
export let updatedTags: string[];
|
|
export let columnLabels: string[];
|
|
export let tagsColumn: number;
|
|
export let guidColumn: number;
|
|
export let preview: StringList[];
|
|
// Protobuf oneofs. Exactly one of these pairs is expected to be set.
|
|
export let notetypeColumn: number | null;
|
|
export let globalNotetype: CsvMetadata_MappedNotetype | null;
|
|
export let deckId: bigint | null;
|
|
export let deckColumn: number | null;
|
|
|
|
let importResponse: ImportResponse | undefined = undefined;
|
|
let lastNotetypeId = globalNotetype?.id;
|
|
let lastDelimeter = delimiter;
|
|
let importing = false;
|
|
|
|
$: columnOptions = getColumnOptions(
|
|
columnLabels,
|
|
preview[0].vals,
|
|
notetypeColumn,
|
|
deckColumn,
|
|
guidColumn,
|
|
);
|
|
$: getCsvMetadata({
|
|
path,
|
|
delimiter,
|
|
notetypeId: undefined,
|
|
deckId: undefined,
|
|
isHtml,
|
|
}).then((meta) => {
|
|
columnLabels = meta.columnLabels;
|
|
preview = meta.preview;
|
|
});
|
|
$: if (globalNotetype?.id !== lastNotetypeId || delimiter !== lastDelimeter) {
|
|
lastNotetypeId = globalNotetype?.id;
|
|
lastDelimeter = delimiter;
|
|
getCsvMetadata({
|
|
path,
|
|
delimiter,
|
|
notetypeId: globalNotetype?.id,
|
|
deckId: deckId ?? undefined,
|
|
}).then((meta) => {
|
|
globalNotetype = tryGetGlobalNotetype(meta);
|
|
deckId = tryGetDeckId(meta);
|
|
tagsColumn = meta.tagsColumn;
|
|
});
|
|
}
|
|
|
|
async function onImport(): Promise<ImportResponse> {
|
|
const result = await importCsv({
|
|
path,
|
|
metadata: {
|
|
dupeResolution,
|
|
matchScope,
|
|
delimiter,
|
|
forceDelimiter,
|
|
isHtml,
|
|
forceIsHtml,
|
|
globalTags,
|
|
updatedTags,
|
|
columnLabels,
|
|
tagsColumn,
|
|
guidColumn,
|
|
deck: buildDeckOneof(deckColumn, deckId),
|
|
notetype: buildNotetypeOneof(globalNotetype, notetypeColumn),
|
|
preview: [],
|
|
},
|
|
});
|
|
await importDone({});
|
|
importing = false;
|
|
return result;
|
|
}
|
|
</script>
|
|
|
|
<div class="outer">
|
|
{#if importing}
|
|
<BackendProgressIndicator task={onImport} bind:result={importResponse} />
|
|
{:else if importResponse}
|
|
<ImportLogPage response={importResponse} params={{ path }} />
|
|
{:else}
|
|
<StickyHeader {path} onImport={() => (importing = true)} />
|
|
|
|
<Container class="csv-page">
|
|
<Row --cols={2}>
|
|
<Col --col-size={1} breakpoint="md">
|
|
<Container>
|
|
<Header heading={tr.importingFile()} />
|
|
<Spacer --height="1.5rem" />
|
|
<DelimiterSelector bind:delimiter disabled={forceDelimiter} />
|
|
<HtmlSwitch bind:isHtml disabled={forceIsHtml} />
|
|
<Preview {columnOptions} {preview} />
|
|
</Container>
|
|
</Col>
|
|
<Col --col-size={1} breakpoint="md">
|
|
<Container>
|
|
<Header heading={tr.importingImportOptions()} />
|
|
<Spacer --height="1.5rem" />
|
|
{#if globalNotetype}
|
|
<NotetypeSelector
|
|
{notetypeNameIds}
|
|
bind:notetypeId={globalNotetype.id}
|
|
/>
|
|
{/if}
|
|
{#if deckId}
|
|
<DeckSelector {deckNameIds} bind:deckId />
|
|
{/if}
|
|
<DupeResolutionSelector bind:dupeResolution />
|
|
<DeckDupeCheckSwitch bind:matchScope />
|
|
<Tags bind:globalTags bind:updatedTags />
|
|
</Container>
|
|
</Col>
|
|
<Col --col-size={1} breakpoint="md">
|
|
<Container>
|
|
<Header heading={tr.importingFieldMapping()} />
|
|
<Spacer --height="1.5rem" />
|
|
<FieldMapper
|
|
{columnOptions}
|
|
bind:globalNotetype
|
|
bind:tagsColumn
|
|
/>
|
|
</Container>
|
|
</Col>
|
|
</Row>
|
|
</Container>
|
|
{/if}
|
|
</div>
|
|
|
|
<style lang="scss">
|
|
.outer {
|
|
margin: 0 auto;
|
|
}
|
|
:global(.csv-page) {
|
|
--gutter-inline: 0.25rem;
|
|
|
|
:global(.row) {
|
|
// rows have negative margins by default
|
|
--bs-gutter-x: 0;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
}
|
|
</style>
|