diff --git a/NEWS.md b/NEWS.md index 6fd5d2e31..55cb85634 100644 --- a/NEWS.md +++ b/NEWS.md @@ -14,16 +14,21 @@ border-radius: 128px; them keeps each one's sort instead of carrying one sort everywhere. A collection you haven't sorted yet keeps whatever sort you arrive with. - Clearing a search puts back the sort the search replaced. - - Sort by Alternate Series (ComicInfo AlternateSeries / AlternateNumber). - Comics group by their alternate series and order by its issue number - inside that group. Filter by an alternate series first to sort by that one - instead of the first one alphabetically. Comics with no alternate series - sort by their real series and issue, so they interleave rather than - clumping, and an alternate series with no issue numbers keeps its comics - in their own issue order. Available in cover view as well as the table. - - Read an alternate series as a reading order: pick it in the reader's - reading-order menu and next/prev follow the alternate numbering. Handy for - using alternate series tags as durable reading lists. + - Sort by Reprints, the other editions an issue was published as. Comics + group by their reprint series and order by its issue number inside that + group. Filter by a reprint first to sort by that one instead of the first + one alphabetically. Comics with no reprints sort by their real series and + issue, so they interleave rather than clumping, and a reprint series with + no issue numbers keeps its comics in their own issue order. Available in + cover view as well as the table. + - Read a reprint series as a reading order: pick it in the reader's + reading-order menu and next/prev follow its numbering. Handy for using + reprint tags as durable reading lists. + - "Alternate Series" is now called "Reprints" everywhere — the sort, the + table column, the filter, the metadata panel and the reader's + reading-order menu. It covers MetronInfo Reprints and localized + AlternativeNames, ComicInfo AlternateSeries and CoMet isVersionOf, so it + is named for all of them rather than for one. - Fixes - Saving browser settings sent an empty request that stored nothing. The diff --git a/codex/choices/browser.py b/codex/choices/browser.py index 011af59dd..369af09e8 100644 --- a/codex/choices/browser.py +++ b/codex/choices/browser.py @@ -18,7 +18,6 @@ { "created_at": "Added Time", "age_rating": "Age Rating", - "reprints": "Alternate Series", "characters": "Characters", "child_count": "Child Count", "community_rating": "Community Rating", @@ -47,6 +46,7 @@ "publisher_name": "Publisher", "date": "Publish Date", "reading_direction": "Reading Direction", + "reprints": "Reprints", "scan_info": "Scan Info", "search_score": "Search Score", "series_name": "Series", @@ -531,11 +531,13 @@ def admin_default_route_for(top_collection: str) -> dict: "edit_widget": None, }, "reprints": { - # Alternate & localized series names (comicbox ``reprints``). - # "Reprints" reads as reprint editions to users, so the - # column, order-by entry and filter all say "Alternate - # Series"; only the ORM key stays ``reprints``. - "label": "Alternate Series", + # Other editions of this issue (comicbox ``reprints``): + # MetronInfo Reprints and localized AlternativeNames, + # ComicInfo AlternateSeries, CoMet isVersionOf. The UI says + # "Reprints" like MetronInfo, comicbox and the ORM key do — + # ComicInfo's "alternate series" names only one of those + # sources and misdescribes the localized names. + "label": "Reprints", "sort_key": "reprints", "m2m": True, "editable": False, diff --git a/codex/collection.py b/codex/collection.py index 974879fac..7a3ec6fca 100644 --- a/codex/collection.py +++ b/codex/collection.py @@ -94,11 +94,11 @@ def collection(self) -> str: } ) -# A reader-only pseudo-collection: the reader can follow an alternate +# A reader-only pseudo-collection: the reader can follow a reprint # series (ComicInfo ``AlternateSeries``) as a reading order, ordered by # ``AlternateNumber``. Deliberately *not* a :class:`Collection` member — -# alternate series aren't browsable, and every map above is exhaustive +# reprints aren't browsable, and every map above is exhaustive # over the enum, so a member without entries would break lookups that # assume a browse route and a cover exists. READER_REPRINT_COLLECTION: Final[str] = "reprints" -READER_REPRINT_LABEL: Final[str] = "Alternate Series" +READER_REPRINT_LABEL: Final[str] = "Reprints" diff --git a/codex/librarian/scribe/importer/const.py b/codex/librarian/scribe/importer/const.py index d5e798c38..702508a98 100644 --- a/codex/librarian/scribe/importer/const.py +++ b/codex/librarian/scribe/importer/const.py @@ -501,7 +501,7 @@ def get_through_model(field: ManyToManyField) -> type[BaseModel]: *COLLECTION_FIELD_NAMES, "story_arc_numbers", "folders", - # Not a browse collection, but the reader reads alternate series as a + # Not a browse collection, but the reader reads reprints as a # reading order off ``Reprint.updated_at``, so a comic leaving one must # re-stamp it the same way a story arc does. REPRINTS_FIELD_NAME, diff --git a/codex/librarian/scribe/importer/query/links_m2m.py b/codex/librarian/scribe/importer/query/links_m2m.py index 0df6e3b1c..03fca77f8 100644 --- a/codex/librarian/scribe/importer/query/links_m2m.py +++ b/codex/librarian/scribe/importer/query/links_m2m.py @@ -73,7 +73,7 @@ def _record_removed_m2m_source_collection( only re-stamps collections a *current* comic still links into. Mirrors the FK move capture in ``CreateComicsImporter``; the delete phase folds these into the force-update map. ``Reprint`` rides along because the - reader reads alternate series as a reading order off its timestamp. + reader reads reprints as a reading order off its timestamp. Tag-style m2ms (genres, characters, …) are not collections and are ignored here. """ diff --git a/codex/librarian/scribe/timestamp_update.py b/codex/librarian/scribe/timestamp_update.py index 7434d2659..af10f3633 100644 --- a/codex/librarian/scribe/timestamp_update.py +++ b/codex/librarian/scribe/timestamp_update.py @@ -18,7 +18,7 @@ # Rows whose ``updated_at`` gates a client-side reload. Browse collections # bust cover caches; ``Reprint`` is not browsable but the reader offers -# alternate series as a reading order, and its arc mtime is read from these +# reprints as a reading order, and its arc mtime is read from these # rows — without a re-stamp an open reader never notices a re-import. _TIMESTAMP_MODELS = (*COLLECTION_MODELS, Reprint) diff --git a/codex/migrations/0053_reprint_issue_number_and_collection_order_memory.py b/codex/migrations/0053_reprint_issue_number_and_collection_order_memory.py index bc01c8b67..6e21b184f 100644 --- a/codex/migrations/0053_reprint_issue_number_and_collection_order_memory.py +++ b/codex/migrations/0053_reprint_issue_number_and_collection_order_memory.py @@ -1,8 +1,8 @@ """ Split Reprint.issue into sortable columns & remember sorts per collection. -The Alternate Series order_by key sorts comics by their ComicInfo -``AlternateNumber`` within an alternate series. ``issue`` is a string, +The Reprints order_by key sorts comics by their ComicInfo +``AlternateNumber`` within a reprint series. ``issue`` is a string, so sorting it directly puts "#10" before "#2"; these derived columns mirror ``Comic.issue_number`` / ``issue_suffix``. @@ -73,7 +73,6 @@ class Migration(migrations.Migration): choices=[ ("created_at", "Added Time"), ("age_rating", "Age Rating"), - ("reprints", "Alternate Series"), ("characters", "Characters"), ("child_count", "Child Count"), ("community_rating", "Community Rating"), @@ -102,6 +101,7 @@ class Migration(migrations.Migration): ("publisher_name", "Publisher"), ("date", "Publish Date"), ("reading_direction", "Reading Direction"), + ("reprints", "Reprints"), ("scan_info", "Scan Info"), ("search_score", "Search Score"), ("series_name", "Series"), diff --git a/codex/migrations/0054_merge_alternate_number_sort.py b/codex/migrations/0054_merge_alternate_number_sort.py deleted file mode 100644 index 5516c58cf..000000000 --- a/codex/migrations/0054_merge_alternate_number_sort.py +++ /dev/null @@ -1,102 +0,0 @@ -""" -Merge the Alternate Number sort into Alternate Series. - -The two sorts each did half the job — ``reprints`` ordered by a display -label (so "#10" preceded "#2") and ``alternate_number`` ordered by the -parsed number but ignored which alternate series it belonged to. They -are now one key, ``reprints``. - -0053 dropped ``alternate_number`` from the field's choices, but choices -aren't enforced by SQLite and 0053 has already been applied on every -database that could hold the value, so the stored settings need this -separate pass. Left in place, a stale key reaches ORDER BY unvalidated -(settings load raw) and raises FieldError. - -The key lives in three places on SettingsBrowser — the ``order_by`` -column, the ``order_extra_keys`` list, and the per-top-collection -``collection_order_memory`` map — and saved views are more rows in the -same table, so every row is remapped regardless of name or client. -""" - -from django.db import migrations - -_OLD_KEY = "alternate_number" -_NEW_KEY = "reprints" - - -def _remap_extra_keys(entries) -> tuple[list, bool]: - """Remap the sort key in an extras list, dropping a duplicate.""" - if not isinstance(entries, list): - return entries, False - remapped: list = [] - changed = False - seen: set = set() - for entry in entries: - key = entry.get("key") if isinstance(entry, dict) else None - if key == _OLD_KEY: - entry = {**entry, "key": _NEW_KEY} # noqa: PLW2901 - key = _NEW_KEY - changed = True - if key in seen: - # The row already sorted by the surviving key. Two entries - # for one column is not a state the sort can express, so the - # first occurrence wins, as the serializer's own extras - # cleaner does. - changed = True - continue - seen.add(key) - remapped.append(entry) - return remapped, changed - - -def _remap_memory(memory) -> tuple[dict, bool]: - """Remap the sort key inside a collection_order_memory map.""" - if not isinstance(memory, dict): - return memory, False - changed = False - for remembered in memory.values(): - if not isinstance(remembered, dict): - continue - if remembered.get("order_by") == _OLD_KEY: - remembered["order_by"] = _NEW_KEY - changed = True - extras, extras_changed = _remap_extra_keys(remembered.get("order_extra_keys")) - if extras_changed: - remembered["order_extra_keys"] = extras - changed = True - return memory, changed - - -def _remap_browser_settings(apps, _schema_editor) -> None: - settings_browser = apps.get_model("codex", "SettingsBrowser") - settings_browser.objects.filter(order_by=_OLD_KEY).update(order_by=_NEW_KEY) - # JSON payloads need a python pass; JSONField key lookups are - # unsupported on SQLite, so scan and rewrite sparsely. - rows = [] - for row in settings_browser.objects.only( - "pk", "order_extra_keys", "collection_order_memory" - ): - extras, extras_changed = _remap_extra_keys(row.order_extra_keys) - memory, memory_changed = _remap_memory(row.collection_order_memory) - if extras_changed or memory_changed: - row.order_extra_keys = extras - row.collection_order_memory = memory - rows.append(row) - if rows: - settings_browser.objects.bulk_update( - rows, ["order_extra_keys", "collection_order_memory"] - ) - - -class Migration(migrations.Migration): - """Remap the retired alternate_number sort key onto reprints.""" - - dependencies = [ - ("codex", "0053_reprint_issue_number_and_collection_order_memory"), - ] - - operations = [ - # Irreversible by design: both former keys map onto ``reprints``, - # so a reverse pass can't know which rows to send back. - migrations.RunPython(_remap_browser_settings, migrations.RunPython.noop), - ] diff --git a/codex/models/named.py b/codex/models/named.py index 5aadde84c..e9f7c2b57 100644 --- a/codex/models/named.py +++ b/codex/models/named.py @@ -128,7 +128,7 @@ class Reprint(BaseModel): """ An alternate or localized edition of this issue. - Denormalized on purpose: alternate series names must not become + Denormalized on purpose: reprint series names must not become Series/Volume rows or they'd appear as phantom browser collections. ``series_name`` absorbs comicbox's ``series.sort_name`` when the reprint carries no ``series.name`` (MetronInfo AlternativeNames do @@ -142,7 +142,7 @@ class Reprint(BaseModel): identifier = ForeignKey(Identifier, on_delete=SET_NULL, null=True) # ``issue`` split into its sortable parts, mirroring # ``Comic.issue_number`` / ``issue_suffix``. Without them the - # Alternate Series sort would order "#10" before "#2". Derived + # Reprints sort would order "#10" before "#2". Derived # in ``presave``, never imported directly; unindexed because they're # only read after an indexed join on pk or series_name. issue_number = CoercingDecimalField(decimal_places=2, max_digits=10, null=True) diff --git a/codex/serializers/fields/collection.py b/codex/serializers/fields/collection.py index c66fbbc15..3d381637c 100644 --- a/codex/serializers/fields/collection.py +++ b/codex/serializers/fields/collection.py @@ -25,7 +25,7 @@ class MtimeCollectionField(BrowserRouteCollectionField): Browse routes plus the reader's alternate-series pseudo-collection. The reader probes the mtime of every arc it offers, and one of those - is an alternate series, which has no browse route of its own. Kept + is a reprint series, which has no browse route of its own. Kept separate from :class:`BrowserRouteCollectionField` so a reader-only value can't leak into an actual browse route. """ diff --git a/codex/serializers/fields/reader.py b/codex/serializers/fields/reader.py index 9b4ee07e8..7ae3a35c4 100644 --- a/codex/serializers/fields/reader.py +++ b/codex/serializers/fields/reader.py @@ -7,7 +7,7 @@ # Collections a comic can be read "within". Mostly browse collections; # p/i/root have no arc of their own (params collapses them to series). -# ``reprints`` is the one reader-only entry — an alternate series is a +# ``reprints`` is the one reader-only entry — a reprint series is a # reading order without a browse route. VALID_ARC_COLLECTIONS = ( Collection.SERIES, diff --git a/codex/user_data/restore.py b/codex/user_data/restore.py index d18d2c323..6602c7d5d 100644 --- a/codex/user_data/restore.py +++ b/codex/user_data/restore.py @@ -559,8 +559,12 @@ def _restore_settings_browser( # Sort keys retired into another key. Sort-only, because there was never # an ``alternate_number`` filter column for ``_resolve_filter_column`` -# to look for (0054: the Alternate Number sort merged into Alternate -# Series). +# to look for (the Alternate Number sort merged into Reprints before +# either shipped). +# +# No migration pairs with this: the retired key never reached a release, +# so only a sidecar written by a development build can still carry it — +# and a sidecar outlives the database it came from. _SORT_KEY_RENAMES: Final[dict[str, str]] = { **_LEGACY_KEY_RENAMES, "alternate_number": "reprints", diff --git a/codex/views/browser/browser.py b/codex/views/browser/browser.py index 9fddc3fa7..08639c1b1 100644 --- a/codex/views/browser/browser.py +++ b/codex/views/browser/browser.py @@ -195,7 +195,7 @@ def _add_table_view_sort_annotations(self, qs): # Every key that sorts through elected-value aliases needs them # annotated, extras included — ``_comic_extra_fields`` resolves an # extra to the same aliases the primary uses. An active reprints - # filter names which alternate series the user is looking at, so + # filter names which reprint series the user is looking at, so # the election is narrowed to it instead of picking the # alphabetically first one. reprint_pks = self.params.get("filters", {}).get("reprints", ()) diff --git a/codex/views/browser/columns.py b/codex/views/browser/columns.py index 860818e6f..f00cee034 100644 --- a/codex/views/browser/columns.py +++ b/codex/views/browser/columns.py @@ -107,9 +107,9 @@ output_field=CharField(), ) -# The Alternate Series sort: a field list like the Name sort's, with +# The Reprints sort: a field list like the Name sort's, with # each part preferring the alternate value. ``reprints`` is M2M — a -# comic can be in several alternate series — so each ORDER BY column +# comic can be in several reprint series — so each ORDER BY column # must first collapse to one value per comic, and collapsing columns # independently would pair one reprint's series with another's issue # number. Instead a correlated subquery *elects* one reprint per comic @@ -146,13 +146,13 @@ def _elected_reprint_value(value: str, reprint_pks: tuple | list = ()) -> Subque """Read one annotation off the outer comic's elected reprint.""" election = Reprint.objects.filter(comic=OuterRef("pk"), series_name__gt="") if reprint_pks: - # A reprints filter names which alternate series the user is + # A reprints filter names which reprint series the user is # looking at; elect among those instead of alphabetically. election = election.filter(pk__in=reprint_pks) election = election.annotate( _series_key=Lower("series_name"), _language_key=Lower("language"), - # An alternate series entry with no issue at all offers nothing + # A reprint entry with no issue at all offers nothing # to sort by; null both parts so the outer Coalesce falls back # to the comic's own issue *jointly*, never half-and-half. _no_issue=Case( @@ -175,15 +175,15 @@ def _elected_reprint_value(value: str, reprint_pks: tuple | list = ()) -> Subque def reprints_sort_annotations(reprint_pks: tuple | list = ()) -> dict: """ - Build the ordered ORDER BY aliases for the Alternate Series sort. + Build the ordered ORDER BY aliases for the Reprints sort. - Alternate series identity (name, volume, language — ``Reprint``'s + Reprint series identity (name, volume, language — ``Reprint``'s unique key minus the issue) leads so every issue of one alternate series groups together, then its parsed issue number and suffix - order the group. A comic with no alternate series falls back to its + order the group. A comic with no reprints falls back to its own series and issue so mixed listings interleave. The fallback series segment is ``Lower(name)`` rather than ``sort_name`` — - alternate series names sort by their raw name, so the article-moved + reprint series names sort by their raw name, so the article-moved ``sort_name`` would put "The Batman" and its untagged siblings at opposite ends of the listing. @@ -391,7 +391,7 @@ def m2m_columns() -> frozenset[str]: # plain display aggregate. ``reprints`` is the only one: an alternate # series has a series name *and* an issue number, so sorting it by the # display label would order "#10" before "#2", and a comic carrying no -# alternate series would park in one undifferentiated clump. Other M2M +# reprints would park in one undifferentiated clump. Other M2M # columns (genres, tags, …) have no such structure and keep the # aggregate sort. diff --git a/codex/views/browser/intersections.py b/codex/views/browser/intersections.py index c66b0fc34..38411a253 100644 --- a/codex/views/browser/intersections.py +++ b/codex/views/browser/intersections.py @@ -808,7 +808,7 @@ def _build_reprints_intersection_sort_sql( return None # One composed key per shared reprint, mirroring the field order of # ``codex.views.browser.columns.reprints_sort_annotations``: - # alternate series identity (name, volume, language) then the issue + # reprint series identity (name, volume, language) then the issue # rendered at fixed width so "#2" collates before "#10". The key # reads ONLY reprint columns — the envelope selects ``display_name`` # as a bare column under ``GROUP BY target_id``, so anything read @@ -841,7 +841,7 @@ def _build_reprints_intersection_sort_sql( # RawSQL in a Django ``Coalesce`` would restore the correlated # subquery to the GROUP BY that ``_IntersectionSortRawSQL`` exists # to keep it out of. An empty intersection (children share no - # alternate series, or disagree) sorts by the collection's own name. + # reprint series, or disagree) sorts by the collection's own name. own = _collection_own_sort_sql(collection_model) sql = f"COALESCE(NULLIF({envelope}, ''), {own})" return _IntersectionSortRawSQL(sql, []) diff --git a/codex/views/browser/mtime.py b/codex/views/browser/mtime.py index 053ee0cb2..c19b60ce0 100644 --- a/codex/views/browser/mtime.py +++ b/codex/views/browser/mtime.py @@ -33,7 +33,7 @@ class MtimeView(BrowserCollectionMtimeView): def _get_reprint_mtime(self, pks): """ - Get the mtime of an alternate series the reader is reading. + Get the mtime of a reprint series the reader is reading. ``Reprint`` isn't a browse collection, so it has no filtered queryset to aggregate — read its rows directly. diff --git a/codex/views/browser/order_by.py b/codex/views/browser/order_by.py index 8230ca665..6c6783d2d 100644 --- a/codex/views/browser/order_by.py +++ b/codex/views/browser/order_by.py @@ -132,9 +132,9 @@ def _comic_order_fields_head(self, order_key: str, comic_sort_names) -> list: if order_key in m2m_sort_columns(): # M2M sort through elected-value aliases (``reprints``): a # field list like the ``sort_name`` head, each part read - # from the comic's elected alternate series with the + # from the comic's elected reprint series with the # comic's own series and issue as the fallback, so comics - # carrying no alternate series interleave by their real + # carrying no reprints interleave by their real # series instead of clumping under an empty list. return list(m2m_sort_order_fields(order_key)) if order_key in m2m_columns(): diff --git a/codex/views/const.py b/codex/views/const.py index 093a90ff3..f666408df 100644 --- a/codex/views/const.py +++ b/codex/views/const.py @@ -82,7 +82,7 @@ COMIC_COLLECTION: "pk", FOLDER_COLLECTION: "parent_folder", STORY_ARC_COLLECTION: "story_arc_numbers__story_arc", - # Reader-only: alternate series are a reading order, not a browse + # Reader-only: reprints are a reading order, not a browse # collection. Browse callers only ever index this map with a # URL-regex-validated collection, so the extra key is inert there. READER_REPRINT_COLLECTION: "reprints", diff --git a/codex/views/reader/arcs.py b/codex/views/reader/arcs.py index 6b9c616c2..e9680a42c 100644 --- a/codex/views/reader/arcs.py +++ b/codex/views/reader/arcs.py @@ -34,7 +34,7 @@ # Arc collections whose rows are groups rather than a single row, so the # requested ids may be a stale subset of the current group. Both story -# arcs (grouped by sort_name) and alternate series (grouped by identity) +# arcs (grouped by sort_name) and reprints (grouped by identity) # accept an intersecting id set as the same arc. _MULTI_ROW_ARC_COLLECTIONS = frozenset( {STORY_ARC_COLLECTION, READER_REPRINT_COLLECTION} @@ -136,7 +136,7 @@ def _get_story_arcs(self, comic: Comic, arcs, max_mtime: int | None): def _get_reprint_arcs(self, comic: Comic, arcs, max_mtime: int | None): """Append the alternate series (ComicInfo AlternateSeries) arcs.""" - # An alternate series is identified by everything but the issue — + # A reprint series is identified by everything but the issue — # that's ``Reprint``'s unique key minus ``issue``. Splitting on # volume and language keeps a v1 and a v2, or an English and a # Spanish edition, from merging into one reading order. @@ -156,7 +156,7 @@ def _get_reprint_arcs(self, comic: Comic, arcs, max_mtime: int | None): language=language, ) - # Every issue of an alternate series is its own ``Reprint`` row, so + # Every issue of a reprint series is its own ``Reprint`` row, so # the arc handle has to be the whole group's pks, not just this # comic's. Keying on one comic's row would make the *next* book # report a different id set and silently drop the reading order. @@ -183,7 +183,7 @@ def _get_reprint_arcs(self, comic: Comic, arcs, max_mtime: int | None): def _fallback_arc_collection(arcs) -> str: """Pick a collection this comic actually has an arc for.""" # The requested collection can be valid yet absent for this comic - # (an alternate series the comic isn't in, a story arc it lost on + # (a reprint series the comic isn't in, a story arc it lost on # re-tag). Reading must still work, so fall back to the most # series-like arc available instead of raising. for collection in _ARC_COLLECTION_FALLBACK_ORDER: diff --git a/frontend/src/components/browser/card/order-by-caption.vue b/frontend/src/components/browser/card/order-by-caption.vue index 3a74830ca..6ca53c243 100644 --- a/frontend/src/components/browser/card/order-by-caption.vue +++ b/frontend/src/components/browser/card/order-by-caption.vue @@ -105,7 +105,7 @@ export default { return n.toFixed(2).replace(/\.?0+$/, ""); }, /* - * The alternate series order_value is the JSON array the table cell + * The reprint series order_value is the JSON array the table cell * renders. Collection rows sort by a fallback the caption can't * show, so only comics get a caption. */ diff --git a/frontend/src/components/browser/toolbars/top/filter-sub-menu.vue b/frontend/src/components/browser/toolbars/top/filter-sub-menu.vue index f36443b60..747b8fbfc 100644 --- a/frontend/src/components/browser/toolbars/top/filter-sub-menu.vue +++ b/frontend/src/components/browser/toolbars/top/filter-sub-menu.vue @@ -128,7 +128,7 @@ const FILTER_TITLE_OVERRIDES = { ageRatingMetron: "Age Rating", // "Reprints" reads as reprint editions; these are alternate and // localized series names. - reprints: "Alternate Series", + reprints: "Reprints", }; // Tab values are the filter keys; "Standardized" is the default tab. const AGE_RATING_DEFAULT_TAB = "ageRatingMetron"; diff --git a/frontend/src/components/metadata/edit-mode/edit-panel.vue b/frontend/src/components/metadata/edit-mode/edit-panel.vue index 03fbba135..ce6c48302 100644 --- a/frontend/src/components/metadata/edit-mode/edit-panel.vue +++ b/frontend/src/components/metadata/edit-mode/edit-panel.vue @@ -1037,7 +1037,7 @@ -