Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
3715a7d
Move `isValidURL` to separate file
mwiencek May 29, 2025
bc72196
Move `getUnicodeUrl` to separate file
mwiencek May 29, 2025
70deca7
Move external-links-editor types to separate file
mwiencek May 29, 2025
f87b02d
Add 'T' suffix to all external-links-editor types
mwiencek May 30, 2025
4744870
Move external-links-editor `TypeDescription` component to separate file
mwiencek May 29, 2025
bc29db6
Make `TypeDescription` flow-strict
mwiencek May 29, 2025
2a46ba3
Move `LinkTypeSelect` component to separate file
mwiencek May 29, 2025
07ea40b
Make `LinkTypeSelect` flow-strict-local
mwiencek May 29, 2025
8a85209
Move `ExternalLinkRelationship` component to separate file
mwiencek May 29, 2025
f5376cd
Move `ExternalLink` component to separate file
mwiencek May 29, 2025
6635c3f
Move edit/externalLinks.js to external-links-editor/components/Extern…
mwiencek Jun 1, 2025
24df5c2
Move `URLInputPopover` from edit/ to external-links-editor/
mwiencek Aug 27, 2025
96aa8d5
Move `ExternalLinkAttributeDialog` and `UrlRelationshipCreditFieldset`
mwiencek Jul 22, 2025
bc8b784
Convert ExternalLink.js to Flow component syntax
mwiencek Jun 1, 2025
5ed88a5
Convert ExternalLinkRelationship.js to Flow component syntax
mwiencek Jun 1, 2025
ee6fdc5
Move `getFormData` out of the `ExternalLinksEditor` class
mwiencek Jun 1, 2025
a0ebb4c
Update weight-balanced-tree to v0.16.0
mwiencek Jun 23, 2025
d3717d2
Clarify some refinements in URLCleanup.js
mwiencek Jun 23, 2025
8315cdc
Refactor `getSourceEntityData`
mwiencek Jul 17, 2025
7ba635e
Change weird boolean types in URLCleanup.js
mwiencek Aug 25, 2025
26f6fdd
Remove repeated sorting from within a loop in `filterApplicableTypes`
mwiencek Aug 25, 2025
44dc6cc
Remove `getPossibleTypes`
mwiencek Aug 25, 2025
a3d3b33
Remove confusing/redundant type
mwiencek Aug 25, 2025
baa26c6
Replace `arraysEqual` call with set comparison
mwiencek Aug 25, 2025
b8d2f08
Use the three-argument form of `useReducer`
mwiencek Aug 27, 2025
d467cb0
Add `advanceUniqueId`
mwiencek Aug 28, 2025
3daac52
Remove unused attributes code in `edit.relationshipEdit`
mwiencek Aug 28, 2025
dbd0369
Define types for seeded release data
mwiencek Aug 29, 2025
9c46a3d
Pull seeded release data from the catalyst stash
mwiencek Aug 29, 2025
bd93095
Remove unused `this.releaseLoaded` fallback
mwiencek Aug 29, 2025
b93dae5
Controller::ReleaseEditor: Serialize new `source_entity` with `isNewE…
mwiencek Feb 5, 2026
ce522e1
Fix react/jsx-no-bind in UrlRelationshipCreditFieldset
mwiencek Feb 5, 2026
88d4a17
Rename `ExternalLinkAttributeDialog` to `ExternalLinkRelationshipDialog`
mwiencek Mar 6, 2026
2c75280
Define types for the new external links editor code
mwiencek Mar 6, 2026
59ac45f
Define constants for the new external links editor code
mwiencek Mar 6, 2026
0638c12
Add `cleanupUrl` utility
mwiencek Mar 6, 2026
b519578
Add `getLinkChecker` utility
mwiencek Mar 6, 2026
c14bc71
Add `getLinkPhrase` external-links-editor utility
mwiencek Mar 6, 2026
d7a9839
Add `isLinkStateEmpty` utility
mwiencek Mar 6, 2026
267ccb7
Add `doesUrlMatchOnlyOnePossibleType` utility
mwiencek Mar 6, 2026
bf9367c
Add `shouldShowTypeSelection` utility
mwiencek Mar 6, 2026
c11aeed
Add `getLinkRelationshipStatus` utility
mwiencek Mar 6, 2026
1278a7e
Add external-links-editor validation module
mwiencek Mar 6, 2026
5bc2dca
Add external-links-editor state module
mwiencek Mar 6, 2026
811f718
Add `StandaloneExternalLinksEditor` component
mwiencek Apr 7, 2026
57f0ec2
Move inline `LinkTypeSelect` props
mwiencek Apr 8, 2026
8cbfbc3
External links editor refactor
mwiencek Jun 1, 2025
a7df8ac
Remove old/unused external-links-editor code
mwiencek Mar 6, 2026
aab16db
Move external-links-editor types module
mwiencek Mar 6, 2026
ba9db8a
Move `ExternalLinksEditor` component
mwiencek Mar 6, 2026
05a41f2
Remove unused ESLint rule exclusions for root/static/scripts/external…
mwiencek Aug 28, 2025
e663a4a
MBS-13716: Upgrade React to v19
mwiencek Aug 28, 2025
9e79658
Handle .eu in `isMusicBrainz` link utility
mwiencek Feb 12, 2026
202e80c
Add `deepFreezeInDevelopment`
mwiencek Feb 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ env:
MTCAPTCHA_PUBLIC_KEY: ${{ secrets.MTCAPTCHA_PUBLIC_KEY }}
MTCAPTCHA_PRIVATE_KEY: ${{ secrets.MTCAPTCHA_PRIVATE_KEY }}
MTCAPTCHA_PRIVATE_TEST_KEY: ${{ secrets.MTCAPTCHA_PRIVATE_TEST_KEY }}
TESTS_IMAGE_TAG: v-2026-03-31.0
TESTS_IMAGE_TAG: v-2026-03-31.1
SECOND_PERL_VERSION: 5.42.0

jobs:
Expand Down
16 changes: 1 addition & 15 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -985,20 +985,11 @@ export default [
{
files: [
'root/static/scripts/common/components/TagEditor.js',
'root/static/scripts/edit/externalLinks.js',
],
rules: {
'react/no-access-state-in-setstate': 'off',
},
},
{
files: [
'root/static/scripts/edit/externalLinks.js',
],
rules: {
'react/no-multi-comp': 'off',
},
},
{
files: [
'root/search/components/ArtistResults.js',
Expand All @@ -1012,13 +1003,9 @@ export default [
'root/static/scripts/common/components/CDTocReleaseListRow.js',
'root/static/scripts/common/components/TagEditor.js',
'root/static/scripts/edit/check-duplicates.js',
'root/static/scripts/edit/components/ExternalLinkAttributeDialog.js',
'root/static/scripts/edit/components/FormRowNameWithGuessCase.js',
'root/static/scripts/edit/components/FormRowSelectList.js',
'root/static/scripts/edit/components/ReleaseMergeStrategy.js',
'root/static/scripts/edit/components/URLInputPopover.js',
'root/static/scripts/edit/components/UrlRelationshipCreditFieldset.js',
'root/static/scripts/edit/externalLinks.js',
'root/static/scripts/event/components/EventEditForm.js',
'root/static/scripts/relationship-editor/components/DialogPreview.js',
],
Expand All @@ -1034,7 +1021,6 @@ export default [
'root/static/scripts/common/i18n/expand2.js',
'root/static/scripts/common/utility/cloneDeep.mjs',
'root/static/scripts/edit/components/withLoadedTypeInfo.js',
'root/static/scripts/edit/externalLinks.js',
'root/static/scripts/relationship-editor/components/DialogEntityCredit.js',
'root/static/scripts/relationship-editor/components/DialogTargetType.js',
'root/static/scripts/relationship-editor/components/RelationshipEditor.js',
Expand Down Expand Up @@ -1068,12 +1054,12 @@ export default [
'root/static/scripts/common/utility/createFastObjectCloneFunction.js',
'root/static/scripts/edit/components/ArtistCreditEditor.js',
'root/static/scripts/edit/components/ArtistCreditEditor/utilities.js',
'root/static/scripts/edit/components/ExternalLinkAttributeDialog.js',
'root/static/scripts/edit/components/Multiselect.js',
'root/static/scripts/edit/components/withLoadedTypeInfo.js',
'root/static/scripts/edit/utility/compactEntityJson.js',
'root/static/scripts/edit/utility/reducerWithErrorHandling.js',
'root/static/scripts/edit/utility/subfieldErrors.js',
'root/static/scripts/external-links-editor/components/ExternalLinkRelationshipDialog.js',
'root/static/scripts/guess-case/MB/GuessCase/Main.js',
'root/static/scripts/relationship-editor/components/DialogAttribute/MultiselectAttribute.js',
'root/static/scripts/relationship-editor/components/DialogEntityCredit.js',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// flow-typed signature: bd8a9984746306d26194a489f3aeff35
// flow-typed version: 388e9edcf0/react-dom_v18.x.x/flow_>=v0.127.x
// @flow strict

declare module 'react-dom_shared-types' {
/**
Expand Down Expand Up @@ -105,33 +104,13 @@ declare module 'react-dom_shared-types' {
}

declare module 'react-dom' {
import * as React from 'react';

declare var version: string;

declare function findDOMNode(
componentOrElement: Element | ?React.Component<any, any>
): null | Element | Text;

declare function render<ElementType: React.ElementType>(
element: React.MixedElement,
container: Element,
callback?: () => void
): React.ElementRef<ElementType>;

declare function hydrate<ElementType: React.ElementType>(
element: React.MixedElement,
container: Element,
callback?: () => void
): React.ElementRef<ElementType>;

declare function createPortal(
node: React.Node,
container: Element
): React$Portal;

declare function unmountComponentAtNode(container: any): boolean;

declare function flushSync(callback: () => mixed): void;

declare function unstable_batchedUpdates<A, B, C, D, E>(
Expand All @@ -142,15 +121,6 @@ declare module 'react-dom' {
d: D,
e: E
): void;

declare function unstable_renderSubtreeIntoContainer<
ElementType: React.ElementType
>(
parentComponent: React.Component<any, any>,
nextElement: React.MixedElement,
container: any,
callback?: () => void
): React.ElementRef<ElementType>;
}

declare module 'react-dom/client' {
Expand Down Expand Up @@ -216,83 +186,3 @@ declare module 'react-dom/server' {
element: React.Node
): stream$Readable;
}

declare module 'react-dom/test-utils' {
import * as React from 'react';

declare interface Thenable {
then(resolve: () => mixed, reject?: () => mixed): mixed,
}

declare var Simulate: {
[eventName: string]: (
element: Element,
eventData?: { [key: string]: mixed, ... }
) => void,
...
};

declare function renderIntoDocument(
instance: React.MixedElement
): React.Component<any, any>;

declare function mockComponent(
componentClass: React.ElementType,
mockTagName?: string
): { [key: string]: mixed, ... };

declare function isElement(element: React.MixedElement): boolean;

declare function isElementOfType(
element: React.MixedElement,
componentClass: React.ElementType
): boolean;

declare function isDOMComponent(instance: any): boolean;

declare function isCompositeComponent(
instance: React.Component<any, any>
): boolean;

declare function isCompositeComponentWithType(
instance: React.Component<any, any>,
componentClass: React.ElementType
): boolean;

declare function findAllInRenderedTree(
tree: React.Component<any, any>,
test: (child: React.Component<any, any>) => boolean
): Array<React.Component<any, any>>;

declare function scryRenderedDOMComponentsWithClass(
tree: React.Component<any, any>,
className: string
): Array<Element>;

declare function findRenderedDOMComponentWithClass(
tree: React.Component<any, any>,
className: string
): ?Element;

declare function scryRenderedDOMComponentsWithTag(
tree: React.Component<any, any>,
tagName: string
): Array<Element>;

declare function findRenderedDOMComponentWithTag(
tree: React.Component<any, any>,
tagName: string
): ?Element;

declare function scryRenderedComponentsWithType(
tree: React.Component<any, any>,
componentClass: React.ElementType
): Array<React.Component<any, any>>;

declare function findRenderedComponentWithType(
tree: React.Component<any, any>,
componentClass: React.ElementType
): ?React.Component<any, any>;

declare function act(callback: () => void | Thenable): Thenable;
}
1 change: 1 addition & 0 deletions lib/MusicBrainz/Server.pm
Original file line number Diff line number Diff line change
Expand Up @@ -871,6 +871,7 @@ sub TO_JSON {
release_artwork_count
release_cdtoc_count
seeded_relationships
seeded_release_data
series_ordering_types
server_details
server_languages
Expand Down
3 changes: 3 additions & 0 deletions lib/MusicBrainz/Server/Controller/Artist.pm
Original file line number Diff line number Diff line change
Expand Up @@ -893,6 +893,9 @@ sub edit_credit : Chained('credit') PathPart('edit') Edit {
my $artist = $c->stash->{artist};
my $ac = $c->stash->{ac};

# Needed by `getSourceEntityData` on the client.
$c->stash( source_entity => $artist->TO_JSON );

$self->edit_action(
$c,
form => 'EditArtistCredit',
Expand Down
8 changes: 6 additions & 2 deletions lib/MusicBrainz/Server/Controller/ReleaseEditor.pm
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,12 @@ sub _init_release_editor
$c->req->body_params->{redirect_uri}
);

$options{seeded_data} = $c->json->encode($self->_seeded_data($c) // {});
$options{seeded_release_data} = $self->_seeded_data($c);

my @medium_formats = $c->model('MediumFormat')->get_all;
my $discid_formats = [ grep { $_ } map { $_->has_discids ? ($_->id) : () } @medium_formats ];
my %medium_format_dates = map { $_->id => $_->year } @medium_formats;
my $release = $c->stash->{release};

$c->stash(
template => 'release/edit/layout.tt',
Expand All @@ -48,7 +49,7 @@ sub _init_release_editor
statuses => select_options_tree($c, 'ReleaseStatus'),
languages => build_grouped_options($c, language_options($c)),
scripts => build_grouped_options($c, script_options($c)),
source_entity => to_json_object($c->stash->{release}),
source_entity => to_json_object($release) // {entityType => 'release', isNewEntity => \1},
packagings => select_options_tree($c, 'ReleasePackaging'),
countries => select_options($c, 'CountryArea'),
formats => select_options_tree($c, 'MediumFormat'),
Expand Down Expand Up @@ -102,6 +103,9 @@ sub add : Path('/release/add') Edit RequireAuth
$self->_init_release_editor($c, return_to => $return_to);
}

# NOTE-SEEDED-DATA-TYPES-1: The types of the final seeded data format
# are to be kept in sync with root/types/releaseeditor.js.

sub _seeded_data
{
my ($self, $c) = @_;
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@
"pg-cursor": "2.19.0",
"po2json": "https://github.com/metabrainz/po2json.git#ba28b657a4af3a3b3753e874bfc233379011c94b",
"punycode": "2.3.1",
"react": "18.3.1",
"react-dom": "18.3.1",
"react": "19.2.4",
"react-dom": "19.2.4",
"react-table": "7.8.0",
"redux": "4.2.1",
"shell-quote": "1.8.3",
Expand All @@ -63,7 +63,7 @@
"tldts": "7.0.25",
"webpack": "5.105.4",
"webpack-node-externals": "3.0.0",
"weight-balanced-tree": "0.9.0",
"weight-balanced-tree": "0.16.0",
"whatwg-fetch": "3.6.20",
"yargs": "3.10.0"
},
Expand Down
2 changes: 1 addition & 1 deletion root/components/forms.tt
Original file line number Diff line number Diff line change
Expand Up @@ -273,5 +273,5 @@
[%- END -%]

[%- MACRO external_links_editor BLOCK -%]
<div id="external-links-editor-container"></div>
[%- React.embed(c, 'static/scripts/external-links-editor/components/StandaloneExternalLinksEditor') -%]
[%- END -%]
6 changes: 4 additions & 2 deletions root/release/EditRelationships.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,17 @@ import * as React from 'react';
import {CatalystContext} from '../context.mjs';
import Layout from '../layout/index.js';
import manifest from '../static/manifest.mjs';
import {
getSourceEntityData,
} from '../static/scripts/common/utility/catalyst.js';
import ReleaseRelationshipEditor
from '../static/scripts/release/components/ReleaseRelationshipEditor.js';

import ReleaseHeader from './ReleaseHeader.js';

component EditRelationships() {
const $c = React.useContext(CatalystContext);
const release = $c.stash.source_entity;
invariant(release?.entityType === 'release');
const release: ReleaseT = getSourceEntityData($c, 'release');

return (
<Layout
Expand Down
1 change: 0 additions & 1 deletion root/release/edit/layout.tt
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@
[%- END %]
returnTo: "[% return_to | js %]",
redirectURI: "[% redirect_uri | js %]",
seed: [% closing_tag_escape(seeded_data) OR 'null' %]
});
</script>
[%- END -%]
1 change: 1 addition & 0 deletions root/server/components.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,7 @@ export default {
'static/scripts/edit/components/HydratedArtistDateRangeFieldset': (): Promise<mixed> => import('../static/scripts/edit/components/HydratedArtistDateRangeFieldset.js'),
'static/scripts/edit/components/HydratedDateRangeFieldset': (): Promise<mixed> => import('../static/scripts/edit/components/HydratedDateRangeFieldset.js'),
'static/scripts/edit/components/InformationIcon': (): Promise<mixed> => import('../static/scripts/edit/components/InformationIcon.js'),
'static/scripts/external-links-editor/components/StandaloneExternalLinksEditor': (): Promise<mixed> => import('../static/scripts/external-links-editor/components/StandaloneExternalLinksEditor.js'),
'static/scripts/recording/RecordingName': (): Promise<mixed> => import('../static/scripts/recording/RecordingName.js'),
'static/scripts/relationship-editor/components/RelationshipEditorWrapper': (): Promise<mixed> => import('../static/scripts/relationship-editor/components/RelationshipEditorWrapper.js'),
'static/scripts/release-editor/components/EditNoteTab': (): Promise<mixed> => import('../static/scripts/release-editor/components/EditNoteTab.js'),
Expand Down
13 changes: 6 additions & 7 deletions root/static/scripts/admin/components/PossibleSpammersList.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,16 +144,15 @@ function reducer(state: StateT, action: ActionT): StateT {
{type: 'update-spammer-button', const action} => {
return {
...state,
users: tree.update(
state.users,
action.state,
cmpUserState,
(existingValue) => spammerButtonReducer(
users: tree.update(state.users, {
cmp: cmpUserState,
key: action.state,
onConflict: (existingValue) => spammerButtonReducer(
existingValue,
action,
),
onNotFoundThrowError,
),
onNotFound: onNotFoundThrowError,
}),
};
}
{type: 'set-users-fetch-error', const error} => {
Expand Down
9 changes: 7 additions & 2 deletions root/static/scripts/common/entity.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ import DescriptiveLink from './components/DescriptiveLink.js';
import EditorLink from './components/EditorLink.js';
import EntityLink from './components/EntityLink.js';
import {bracketedText} from './utility/bracketed.js';
import {getSourceEntityData} from './utility/catalyst.js';
import {
getCatalystContext,
getSourceEntityData,
} from './utility/catalyst.js';
import clean from './utility/clean.js';
import {cloneArrayDeep, cloneObjectDeep} from './utility/cloneDeep.mjs';
import formatTrackLength from './utility/formatTrackLength.js';
Expand Down Expand Up @@ -136,7 +139,9 @@ import MB from './MB.js';
if (MB._sourceEntityInstance != null) {
return MB._sourceEntityInstance;
}
MB._sourceEntityInstance = MB.entity(getSourceEntityData());
MB._sourceEntityInstance = MB.entity(getSourceEntityData(
getCatalystContext(),
));
return MB._sourceEntityInstance;
};

Expand Down
Loading
Loading