pl-fe: support exclusive lists and replies policy

Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
nicole mikołajczyk
2025-07-19 00:45:25 +02:00
parent d4c9910fc4
commit 63dfa66379
10 changed files with 144 additions and 41 deletions

View File

@ -10,7 +10,7 @@ import { filteredArray } from './utils';
const listSchema = v.object({ const listSchema = v.object({
id: v.pipe(v.unknown(), v.transform(String)), id: v.pipe(v.unknown(), v.transform(String)),
title: v.string(), title: v.string(),
replies_policy: v.fallback(v.optional(v.string()), undefined), replies_policy: v.fallback(v.optional(v.picklist(['none', 'list', 'followed'])), undefined),
exclusive: v.fallback(v.optional(v.boolean()), undefined), exclusive: v.fallback(v.optional(v.boolean()), undefined),
antennas: filteredArray(v.lazy(() => antennaSchema)), antennas: filteredArray(v.lazy(() => antennaSchema)),
notify: v.fallback(v.optional(v.boolean()), undefined), notify: v.fallback(v.optional(v.boolean()), undefined),

View File

@ -1100,6 +1100,11 @@ const getFeatures = (instance: Instance) => {
v.software === TAKAHE && gte(v.version, '0.12.0'), v.software === TAKAHE && gte(v.version, '0.12.0'),
]), ]),
listsExclusive: any([
v.software === GOTOSOCIAL,
v.software === MASTODON && gte(v.version, '4.2.0'),
]),
/** /**
* Can add a list to favourites. * Can add a list to favourites.
* @see POST /api/v1/lists/:list_id/favourite * @see POST /api/v1/lists/:list_id/favourite
@ -1107,6 +1112,11 @@ const getFeatures = (instance: Instance) => {
*/ */
listsFavourites: instance.api_versions['favourite_list.fedibird.pl-api'] >= 1, listsFavourites: instance.api_versions['favourite_list.fedibird.pl-api'] >= 1,
listsRepliesPolicy: any([
v.software === GOTOSOCIAL,
v.software === MASTODON && gte(v.version, '3.3.0'),
]),
/** /**
* Can load latest activities from outbox. * Can load latest activities from outbox.
* @see POST /api/v1/accounts/:id/load_activities * @see POST /api/v1/accounts/:id/load_activities

View File

@ -1,6 +1,6 @@
{ {
"name": "pl-api", "name": "pl-api",
"version": "1.0.0-rc.84", "version": "1.0.0-rc.85",
"type": "module", "type": "module",
"homepage": "https://codeberg.org/mkljczk/pl-fe/src/branch/develop/packages/pl-api", "homepage": "https://codeberg.org/mkljczk/pl-fe/src/branch/develop/packages/pl-api",
"repository": { "repository": {

View File

@ -105,7 +105,7 @@
"multiselect-react-dropdown": "^2.0.25", "multiselect-react-dropdown": "^2.0.25",
"mutative": "^1.1.0", "mutative": "^1.1.0",
"path-browserify": "^1.0.1", "path-browserify": "^1.0.1",
"pl-api": "^1.0.0-rc.84", "pl-api": "^1.0.0-rc.85",
"postcss": "^8.5.3", "postcss": "^8.5.3",
"process": "^0.11.10", "process": "^0.11.10",
"punycode": "^2.1.1", "punycode": "^2.1.1",

View File

@ -11,6 +11,8 @@ import type { Account, List, PaginatedResponse } from 'pl-api';
import type { AppDispatch, RootState } from 'pl-fe/store'; import type { AppDispatch, RootState } from 'pl-fe/store';
const LIST_EDITOR_TITLE_CHANGE = 'LIST_EDITOR_TITLE_CHANGE' as const; const LIST_EDITOR_TITLE_CHANGE = 'LIST_EDITOR_TITLE_CHANGE' as const;
const LIST_EDITOR_REPLIES_POLICY_CHANGE = 'LIST_EDITOR_REPLIES_POLICY_CHANGE' as const;
const LIST_EDITOR_EXCLUSIVE_CHANGE = 'LIST_EDITOR_EXCLUSIVE_CHANGE' as const;
const LIST_EDITOR_RESET = 'LIST_EDITOR_RESET' as const; const LIST_EDITOR_RESET = 'LIST_EDITOR_RESET' as const;
const LIST_EDITOR_SETUP = 'LIST_EDITOR_SETUP' as const; const LIST_EDITOR_SETUP = 'LIST_EDITOR_SETUP' as const;
@ -55,6 +57,16 @@ const changeListEditorTitle = (value: string) => ({
value, value,
}); });
const changeListEditorRepliesPolicy = (repliesPolicy: List['replies_policy']) => ({
type: LIST_EDITOR_REPLIES_POLICY_CHANGE,
repliesPolicy,
});
const changeListEditorExclusive = (exclusive: boolean) => ({
type: LIST_EDITOR_EXCLUSIVE_CHANGE,
exclusive,
});
const resetListEditor = () => ({ const resetListEditor = () => ({
type: LIST_EDITOR_RESET, type: LIST_EDITOR_RESET,
}); });
@ -204,6 +216,8 @@ const removeFromListAdder = (listId: string) => (dispatch: AppDispatch, getState
type ListsAction = type ListsAction =
| ListEditorSetupAction | ListEditorSetupAction
| ReturnType<typeof changeListEditorTitle> | ReturnType<typeof changeListEditorTitle>
| ReturnType<typeof changeListEditorRepliesPolicy>
| ReturnType<typeof changeListEditorExclusive>
| ReturnType<typeof resetListEditor> | ReturnType<typeof resetListEditor>
| ReturnType<typeof fetchListAccountsRequest> | ReturnType<typeof fetchListAccountsRequest>
| ReturnType<typeof fetchListAccountsSuccess> | ReturnType<typeof fetchListAccountsSuccess>
@ -221,6 +235,8 @@ type ListsAction =
export { export {
LIST_EDITOR_TITLE_CHANGE, LIST_EDITOR_TITLE_CHANGE,
LIST_EDITOR_REPLIES_POLICY_CHANGE,
LIST_EDITOR_EXCLUSIVE_CHANGE,
LIST_EDITOR_RESET, LIST_EDITOR_RESET,
LIST_EDITOR_SETUP, LIST_EDITOR_SETUP,
LIST_ACCOUNTS_FETCH_REQUEST, LIST_ACCOUNTS_FETCH_REQUEST,
@ -238,6 +254,8 @@ export {
LIST_ADDER_LISTS_FETCH_FAIL, LIST_ADDER_LISTS_FETCH_FAIL,
setupListEditor, setupListEditor,
changeListEditorTitle, changeListEditorTitle,
changeListEditorRepliesPolicy,
changeListEditorExclusive,
resetListEditor, resetListEditor,
fetchListSuggestions, fetchListSuggestions,
clearListSuggestions, clearListSuggestions,

View File

@ -188,11 +188,11 @@
"admin.relays.new.url_placeholder": "Instance relay URL", "admin.relays.new.url_placeholder": "Instance relay URL",
"admin.relays.unfollow": "Unfollow", "admin.relays.unfollow": "Unfollow",
"admin.relays.url": "Instance URL:", "admin.relays.url": "Instance URL:",
"admin.reports.actions.close": "Close", "admin.reports.account": "Reported by:",
"admin.reports.actions.view_status": "View post", "admin.reports.actions.view_status": "View post",
"admin.reports.comment": "Comment:",
"admin.reports.empty_message": "There are no open reports. If a user gets reported, they will show up here.", "admin.reports.empty_message": "There are no open reports. If a user gets reported, they will show up here.",
"admin.reports.report_closed_message": "Report on @{name} was closed", "admin.reports.statuses": "Reported posts:",
"admin.reports.report_title": "Report on {acct}",
"admin.rule.priority": "Priority:", "admin.rule.priority": "Priority:",
"admin.rules.action": "Create rule", "admin.rules.action": "Create rule",
"admin.rules.delete": "Delete", "admin.rules.delete": "Delete",
@ -459,6 +459,7 @@
"column.reactions": "Reactions", "column.reactions": "Reactions",
"column.reblogs": "Reposts", "column.reblogs": "Reposts",
"column.registration": "Sign up", "column.registration": "Sign up",
"column.report": "Report #{id}",
"column.scheduled_statuses": "Scheduled posts", "column.scheduled_statuses": "Scheduled posts",
"column.search": "Search", "column.search": "Search",
"column.settings_store": "Settings store", "column.settings_store": "Settings store",
@ -1076,11 +1077,18 @@
"lists.account.remove": "Remove from list", "lists.account.remove": "Remove from list",
"lists.delete": "Delete list", "lists.delete": "Delete list",
"lists.edit": "Edit list", "lists.edit": "Edit list",
"lists.edit.submit": "Change title", "lists.edit.save": "Save list",
"lists.edit.show_replies_to": "Include replies from list members to",
"lists.edit.title": "List title",
"lists.exclusive": "Hide members in Home",
"lists.exclusive_hint": "If someone is on this list, hide them in your Home feed to avoid seeing their posts twice.",
"lists.new.create": "Add list", "lists.new.create": "Add list",
"lists.new.create_title": "Add list", "lists.new.create_title": "Add list",
"lists.new.save_title": "Save title", "lists.new.save": "Save list",
"lists.new.title_placeholder": "New list title", "lists.new.title_placeholder": "New list title",
"lists.replies_policy.followed": "Any followed user",
"lists.replies_policy.list": "Members of the list",
"lists.replies_policy.none": "No one",
"lists.search": "Search among people you follow", "lists.search": "Search among people you follow",
"lists.subheading": "Your lists", "lists.subheading": "Your lists",
"loading_indicator.label": "Loading…", "loading_indicator.label": "Loading…",

View File

@ -1,25 +1,33 @@
import React from 'react'; import React from 'react';
import { defineMessages, useIntl } from 'react-intl'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { changeListEditorTitle } from 'pl-fe/actions/lists'; import { changeListEditorExclusive, changeListEditorRepliesPolicy, changeListEditorTitle } from 'pl-fe/actions/lists';
import List, { ListItem } from 'pl-fe/components/list';
import Button from 'pl-fe/components/ui/button'; import Button from 'pl-fe/components/ui/button';
import Form from 'pl-fe/components/ui/form'; import Form from 'pl-fe/components/ui/form';
import HStack from 'pl-fe/components/ui/hstack'; import FormActions from 'pl-fe/components/ui/form-actions';
import FormGroup from 'pl-fe/components/ui/form-group';
import Input from 'pl-fe/components/ui/input'; import Input from 'pl-fe/components/ui/input';
import Toggle from 'pl-fe/components/ui/toggle';
import { SelectDropdown } from 'pl-fe/features/forms';
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch'; import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
import { useAppSelector } from 'pl-fe/hooks/use-app-selector'; import { useAppSelector } from 'pl-fe/hooks/use-app-selector';
import { useFeatures } from 'pl-fe/hooks/use-features';
import { useUpdateList } from 'pl-fe/queries/accounts/use-lists'; import { useUpdateList } from 'pl-fe/queries/accounts/use-lists';
const messages = defineMessages({ const messages = defineMessages({
title: { id: 'lists.edit.submit', defaultMessage: 'Change title' }, save: { id: 'lists.new.save', defaultMessage: 'Save list' },
save: { id: 'lists.new.save_title', defaultMessage: 'Save title' }, repliesPolicyNone: { id: 'lists.replies_policy.none', defaultMessage: 'No one' },
repliesPolicyList: { id: 'lists.replies_policy.list', defaultMessage: 'Members of the list' },
repliesPolicyFollowed: { id: 'lists.replies_policy.followed', defaultMessage: 'Any followed user' },
}); });
const ListForm = () => { const ListForm = () => {
const intl = useIntl(); const intl = useIntl();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const features = useFeatures();
const { title: value, listId } = useAppSelector((state) => state.listEditor); const { title: value, listId, repliesPolicy, exclusive } = useAppSelector((state) => state.listEditor);
const { mutate: updateList, isPending: disabled } = useUpdateList(listId!); const { mutate: updateList, isPending: disabled } = useUpdateList(listId!);
@ -29,29 +37,73 @@ const ListForm = () => {
const handleSubmit: React.FormEventHandler<Element> = e => { const handleSubmit: React.FormEventHandler<Element> = e => {
e.preventDefault(); e.preventDefault();
updateList({ title: value }); updateList({ title: value, replies_policy: repliesPolicy, exclusive });
}; };
const handleClick = () => { const handleClick = () => {
updateList({ title: value }); updateList({ title: value, replies_policy: repliesPolicy, exclusive });
}; };
const save = intl.formatMessage(messages.save); const handleChangeRepliesPolicy = (e: React.ChangeEvent<HTMLSelectElement>) => {
dispatch(changeListEditorRepliesPolicy(e.target.value as 'none'));
};
const handleChangeExclusive = (e: React.ChangeEvent<HTMLInputElement>) => {
dispatch(changeListEditorExclusive(e.target.checked));
};
return ( return (
<Form onSubmit={handleSubmit}> <Form onSubmit={handleSubmit}>
<HStack space={2}> <FormGroup
labelText={<FormattedMessage id='lists.edit.title' defaultMessage='List title' />}
>
<Input <Input
outerClassName='grow' outerClassName='grow'
type='text' type='text'
value={value} value={value}
onChange={handleChange} onChange={handleChange}
/> />
</FormGroup>
{(features.listsRepliesPolicy || features.listsExclusive) && (
<List>
{features.listsRepliesPolicy && (
<ListItem
label={<FormattedMessage id='lists.edit.show_replies_to' defaultMessage='Include replies from list members to' />}
>
<SelectDropdown
key={repliesPolicy}
className='max-w-fit'
items={{
none: intl.formatMessage(messages.repliesPolicyNone),
list: intl.formatMessage(messages.repliesPolicyList),
followed: intl.formatMessage(messages.repliesPolicyFollowed),
}}
defaultValue={repliesPolicy || 'list'}
onChange={handleChangeRepliesPolicy}
/>
</ListItem>
)}
{features.listsExclusive && (
<ListItem
label={<FormattedMessage id='lists.exclusive' defaultMessage='Hide members in Home' />}
hint={<FormattedMessage id='lists.exclusive_hint' defaultMessage='If someone is on this list, hide them in your Home feed to avoid seeing their posts twice.' />}
>
<Toggle
checked={exclusive}
onChange={handleChangeExclusive}
/>
</ListItem>
)}
</List>
)}
<FormActions>
<Button onClick={handleClick} disabled={disabled}> <Button onClick={handleClick} disabled={disabled}>
{save} <FormattedMessage id='lists.edit.save' defaultMessage='Save list' />
</Button> </Button>
</HStack> </FormActions>
</Form> </Form>
); );
}; };

View File

@ -14,7 +14,6 @@ import Search from './components/search';
import type { BaseModalProps } from 'pl-fe/features/ui/components/modal-root'; import type { BaseModalProps } from 'pl-fe/features/ui/components/modal-root';
const messages = defineMessages({ const messages = defineMessages({
changeTitle: { id: 'lists.edit.submit', defaultMessage: 'Change title' },
addToList: { id: 'lists.account.add', defaultMessage: 'Add to list' }, addToList: { id: 'lists.account.add', defaultMessage: 'Add to list' },
removeFromList: { id: 'lists.account.remove', defaultMessage: 'Remove from list' }, removeFromList: { id: 'lists.account.remove', defaultMessage: 'Remove from list' },
editList: { id: 'lists.edit', defaultMessage: 'Edit list' }, editList: { id: 'lists.edit', defaultMessage: 'Edit list' },
@ -48,11 +47,7 @@ const ListEditorModal: React.FC<BaseModalProps & ListEditorModalProps> = ({ list
title={<FormattedMessage id='lists.edit' defaultMessage='Edit list' />} title={<FormattedMessage id='lists.edit' defaultMessage='Edit list' />}
onClose={onClickClose} onClose={onClickClose}
> >
<CardHeader>
<CardTitle title={intl.formatMessage(messages.changeTitle)} />
</CardHeader>
<EditListForm /> <EditListForm />
<br />
{accountIds.length > 0 && ( {accountIds.length > 0 && (
<div> <div>

View File

@ -13,12 +13,19 @@ import {
LIST_EDITOR_ADD_SUCCESS, LIST_EDITOR_ADD_SUCCESS,
LIST_EDITOR_REMOVE_SUCCESS, LIST_EDITOR_REMOVE_SUCCESS,
type ListsAction, type ListsAction,
LIST_EDITOR_EXCLUSIVE_CHANGE,
LIST_EDITOR_REPLIES_POLICY_CHANGE,
} from '../actions/lists'; } from '../actions/lists';
import type { List } from 'pl-api';
interface State { interface State {
listId: string | null; listId: string | null;
isSubmitting: boolean; isSubmitting: boolean;
title: string; title: string;
repliesPolicy: List['replies_policy'];
exclusive?: boolean;
accounts: { accounts: {
items: Array<string>; items: Array<string>;
@ -36,6 +43,8 @@ const initialState: State = {
listId: null, listId: null,
isSubmitting: false, isSubmitting: false,
title: '', title: '',
repliesPolicy: undefined,
exclusive: false,
accounts: { accounts: {
items: [], items: [],
@ -54,15 +63,26 @@ const listEditorReducer = (state: State = initialState, action: ListsAction): St
case LIST_EDITOR_RESET: case LIST_EDITOR_RESET:
return initialState; return initialState;
case LIST_EDITOR_SETUP: case LIST_EDITOR_SETUP:
console.log(action.list);
return create(state, (draft) => { return create(state, (draft) => {
draft.listId = action.list.id; draft.listId = action.list.id;
draft.title = action.list.title; draft.title = action.list.title;
draft.repliesPolicy = action.list.replies_policy;
draft.exclusive = action.list.exclusive;
draft.isSubmitting = false; draft.isSubmitting = false;
}); });
case LIST_EDITOR_TITLE_CHANGE: case LIST_EDITOR_TITLE_CHANGE:
return create(state, (draft) => { return create(state, (draft) => {
draft.title = action.value; draft.title = action.value;
}); });
case LIST_EDITOR_EXCLUSIVE_CHANGE:
return create(state, (draft) => {
draft.exclusive = action.exclusive;
});
case LIST_EDITOR_REPLIES_POLICY_CHANGE:
return create(state, (draft) => {
draft.repliesPolicy = action.repliesPolicy;
});
case LIST_ACCOUNTS_FETCH_REQUEST: case LIST_ACCOUNTS_FETCH_REQUEST:
return create(state, (draft) => { return create(state, (draft) => {
draft.accounts.isLoading = true; draft.accounts.isLoading = true;

View File

@ -5913,10 +5913,10 @@ isomorphic-dompurify@^2.19.0:
dompurify "^3.2.3" dompurify "^3.2.3"
jsdom "^25.0.1" jsdom "^25.0.1"
isows@^1.0.6: isows@^1.0.7:
version "1.0.6" version "1.0.7"
resolved "https://registry.yarnpkg.com/isows/-/isows-1.0.6.tgz#0da29d706fa51551c663c627ace42769850f86e7" resolved "https://registry.yarnpkg.com/isows/-/isows-1.0.7.tgz#1c06400b7eed216fbba3bcbd68f12490fc342915"
integrity sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw== integrity sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==
iterator.prototype@^1.1.4: iterator.prototype@^1.1.4:
version "1.1.4" version "1.1.4"
@ -6998,19 +6998,19 @@ pkg-dir@^4.1.0:
dependencies: dependencies:
find-up "^4.0.0" find-up "^4.0.0"
pl-api@^1.0.0-rc.84: pl-api@^1.0.0-rc.85:
version "1.0.0-rc.84" version "1.0.0-rc.85"
resolved "https://registry.yarnpkg.com/pl-api/-/pl-api-1.0.0-rc.84.tgz#3bb2a3befee692159ecae80b10a6fd5fd01705fa" resolved "https://registry.yarnpkg.com/pl-api/-/pl-api-1.0.0-rc.85.tgz#2e23aeb55506f6fe896e4b6f894eee090f36d63b"
integrity sha512-8STLCsVEqNynFUoXTECpzPbpdsyEQXJcgdSWLXQA/KghQeFAUfVMzCY8v6BEK2N5CmG+qFGDmCxiWaFlwchP3g== integrity sha512-G6pSQhd7mANwZajTls4RPSV20GXqfKTOI6lNuB0HaYhl3bmDHxcv+RFsUAIvx75hK5/WJzNg08hY4sqdZSsWXw==
dependencies: dependencies:
blurhash "^2.0.5" blurhash "^2.0.5"
http-link-header "^1.1.3" http-link-header "^1.1.3"
isows "^1.0.6" isows "^1.0.7"
lodash.omit "^4.5.0" lodash.omit "^4.5.0"
lodash.pick "^4.4.0" lodash.pick "^4.4.0"
object-to-formdata "^4.5.1" object-to-formdata "^4.5.1"
query-string "^9.1.1" query-string "^9.2.2"
semver "^7.7.1" semver "^7.7.2"
valibot "^1.1.0" valibot "^1.1.0"
possible-typed-array-names@^1.0.0: possible-typed-array-names@^1.0.0:
@ -7356,7 +7356,7 @@ qrcode.react@^4.2.0:
resolved "https://registry.yarnpkg.com/qrcode.react/-/qrcode.react-4.2.0.tgz#1bce8363f348197d145c0da640929a24c83cbca3" resolved "https://registry.yarnpkg.com/qrcode.react/-/qrcode.react-4.2.0.tgz#1bce8363f348197d145c0da640929a24c83cbca3"
integrity sha512-QpgqWi8rD9DsS9EP3z7BT+5lY5SFhsqGjpgW5DY/i3mK4M9DTBNz3ErMi8BWYEfI3L0d8GIbGmcdFAS1uIRGjA== integrity sha512-QpgqWi8rD9DsS9EP3z7BT+5lY5SFhsqGjpgW5DY/i3mK4M9DTBNz3ErMi8BWYEfI3L0d8GIbGmcdFAS1uIRGjA==
query-string@^9.1.1, query-string@^9.2.2: query-string@^9.2.2:
version "9.2.2" version "9.2.2"
resolved "https://registry.yarnpkg.com/query-string/-/query-string-9.2.2.tgz#a0104824edfdd2c1db2f18af71cef7abf6a3b20f" resolved "https://registry.yarnpkg.com/query-string/-/query-string-9.2.2.tgz#a0104824edfdd2c1db2f18af71cef7abf6a3b20f"
integrity sha512-pDSIZJ9sFuOp6VnD+5IkakSVf+rICAuuU88Hcsr6AKL0QtxSIfVuKiVP2oahFI7tk3CRSexwV+Ya6MOoTxzg9g== integrity sha512-pDSIZJ9sFuOp6VnD+5IkakSVf+rICAuuU88Hcsr6AKL0QtxSIfVuKiVP2oahFI7tk3CRSexwV+Ya6MOoTxzg9g==
@ -8046,10 +8046,10 @@ semver@^7.5.4, semver@^7.6.0, semver@^7.6.2, semver@^7.6.3:
resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143"
integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
semver@^7.7.1: semver@^7.7.2:
version "7.7.1" version "7.7.2"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f" resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58"
integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA== integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==
serialize-javascript@^6.0.1, serialize-javascript@^6.0.2: serialize-javascript@^6.0.1, serialize-javascript@^6.0.2:
version "6.0.2" version "6.0.2"