diff --git a/app/soapbox/actions/events.ts b/app/soapbox/actions/events.ts index 711dd6a63..ac2cbdf74 100644 --- a/app/soapbox/actions/events.ts +++ b/app/soapbox/actions/events.ts @@ -11,7 +11,7 @@ import snackbar from './snackbar'; import type { AxiosError } from 'axios'; import type { AppDispatch, RootState } from 'soapbox/store'; -import type { APIEntity } from 'soapbox/types/entities'; +import type { APIEntity, Status as StatusEntity } from 'soapbox/types/entities'; const LOCATION_SEARCH_REQUEST = 'LOCATION_SEARCH_REQUEST'; const LOCATION_SEARCH_SUCCESS = 'LOCATION_SEARCH_SUCCESS'; @@ -259,7 +259,7 @@ const joinEvent = (id: string, participationMessage?: string) => return dispatch(noOp); } - dispatch(joinEventRequest()); + dispatch(joinEventRequest(status)); return api(getState).post(`/api/v1/pleroma/events/${id}/join`, { participationMessage }).then(({ data }) => { dispatch(importFetchedStatus(data)); @@ -270,22 +270,24 @@ const joinEvent = (id: string, participationMessage?: string) => `/@${data.account.acct}/events/${data.id}`, )); }).catch(function(error) { - dispatch(joinEventFail(error, status?.event?.join_state || null)); + dispatch(joinEventFail(error, status, status?.event?.join_state || null)); }); }; -const joinEventRequest = () => ({ +const joinEventRequest = (status: StatusEntity) => ({ type: EVENT_JOIN_REQUEST, + id: status.id, }); const joinEventSuccess = (status: APIEntity) => ({ type: EVENT_JOIN_SUCCESS, - status, + id: status.id, }); -const joinEventFail = (error: AxiosError, previousState: string | null) => ({ +const joinEventFail = (error: AxiosError, status: StatusEntity, previousState: string | null) => ({ type: EVENT_JOIN_FAIL, error, + id: status.id, previousState, }); @@ -297,27 +299,29 @@ const leaveEvent = (id: string) => return dispatch(noOp); } - dispatch(leaveEventRequest()); + dispatch(leaveEventRequest(status)); return api(getState).post(`/api/v1/pleroma/events/${id}/leave`).then(({ data }) => { dispatch(importFetchedStatus(data)); dispatch(leaveEventSuccess(data)); }).catch(function(error) { - dispatch(leaveEventFail(error)); + dispatch(leaveEventFail(error, status)); }); }; -const leaveEventRequest = () => ({ +const leaveEventRequest = (status: StatusEntity) => ({ type: EVENT_LEAVE_REQUEST, + id: status.id, }); const leaveEventSuccess = (status: APIEntity) => ({ type: EVENT_LEAVE_SUCCESS, - status, + id: status.id, }); -const leaveEventFail = (error: AxiosError) => ({ +const leaveEventFail = (error: AxiosError, status: StatusEntity) => ({ type: EVENT_LEAVE_FAIL, + id: status.id, error, }); diff --git a/app/soapbox/components/status-media.tsx b/app/soapbox/components/status-media.tsx index 9a0c007e1..c312c8a0c 100644 --- a/app/soapbox/components/status-media.tsx +++ b/app/soapbox/components/status-media.tsx @@ -38,7 +38,7 @@ const StatusMedia: React.FC = ({ const dispatch = useAppDispatch(); const [mediaWrapperWidth, setMediaWrapperWidth] = useState(undefined); - const mediaAttachments = excludeBanner ? status.media_attachments.filter(({ description }) => description !== 'Banner') : status.media_attachments; + const mediaAttachments = excludeBanner ? status.media_attachments.filter(({ description, pleroma }) => description !== 'Banner' && pleroma.get('mime_type') !== 'text/html') : status.media_attachments; const size = mediaAttachments.size; const firstAttachment = mediaAttachments.first(); diff --git a/app/soapbox/components/ui/stack/stack.tsx b/app/soapbox/components/ui/stack/stack.tsx index 6527184db..06d4bba44 100644 --- a/app/soapbox/components/ui/stack/stack.tsx +++ b/app/soapbox/components/ui/stack/stack.tsx @@ -1,7 +1,7 @@ import classNames from 'clsx'; import React from 'react'; -type SIZES = 0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 10 +type SIZES = 0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 10 const spaces = { 0: 'space-y-0', @@ -12,6 +12,7 @@ const spaces = { 3: 'space-y-3', 4: 'space-y-4', 5: 'space-y-5', + 6: 'space-y-6', 10: 'space-y-10', }; diff --git a/app/soapbox/features/event/components/event-action-button.tsx b/app/soapbox/features/event/components/event-action-button.tsx index 0b72ec8ee..cbc186759 100644 --- a/app/soapbox/features/event/components/event-action-button.tsx +++ b/app/soapbox/features/event/components/event-action-button.tsx @@ -4,7 +4,7 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { joinEvent, leaveEvent } from 'soapbox/actions/events'; import { openModal } from 'soapbox/actions/modals'; import { Button } from 'soapbox/components/ui'; -import { useAppDispatch } from 'soapbox/hooks'; +import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; import type { Status as StatusEntity } from 'soapbox/types/entities'; @@ -21,6 +21,8 @@ const EventActionButton: React.FC = ({ status }) => { const intl = useIntl(); const dispatch = useAppDispatch(); + const me = useAppSelector((state) => state.me); + const event = status.event!; const handleJoin: React.EventHandler = (e) => { @@ -49,6 +51,15 @@ const EventActionButton: React.FC = ({ status }) => { } }; + const handleOpenUnauthorizedModal: React.EventHandler = (e) => { + e.preventDefault(); + + dispatch(openModal('UNAUTHORIZED', { + action: 'JOIN', + ap_id: status.url, + })); + }; + let buttonLabel; let buttonIcon; let buttonDisabled = false; @@ -69,7 +80,7 @@ const EventActionButton: React.FC = ({ status }) => { break; default: buttonLabel = ; - buttonAction = handleJoin; + buttonAction = me ? handleJoin : handleOpenUnauthorizedModal; } return ( diff --git a/app/soapbox/features/event/components/event-header.tsx b/app/soapbox/features/event/components/event-header.tsx index e79198e34..8f2cf7ddf 100644 --- a/app/soapbox/features/event/components/event-header.tsx +++ b/app/soapbox/features/event/components/event-header.tsx @@ -4,13 +4,15 @@ import { Link } from 'react-router-dom'; import { fetchEventIcs } from 'soapbox/actions/events'; import { openModal } from 'soapbox/actions/modals'; +import { deleteStatusModal, toggleStatusSensitivityModal } from 'soapbox/actions/moderation'; import Icon from 'soapbox/components/icon'; import StillImage from 'soapbox/components/still_image'; -import { HStack, IconButton, Menu, MenuButton, MenuDivider, MenuItem, MenuLink, MenuList, Stack, Text } from 'soapbox/components/ui'; +import { Button, HStack, IconButton, Menu, MenuButton, MenuDivider, MenuItem, MenuLink, MenuList, Stack, Text } from 'soapbox/components/ui'; import SvgIcon from 'soapbox/components/ui/icon/svg-icon'; import VerificationBadge from 'soapbox/components/verification_badge'; -import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; +import { useAppDispatch, useOwnAccount } from 'soapbox/hooks'; import { download } from 'soapbox/utils/download'; +import { shortNumberFormat } from 'soapbox/utils/numbers'; import PlaceholderEventHeader from '../../placeholder/components/placeholder_event_header'; import EventActionButton from '../components/event-action-button'; @@ -23,6 +25,13 @@ const messages = defineMessages({ bannerHeader: { id: 'event.banner', defaultMessage: 'Event banner' }, exportIcs: { id: 'event.export_ics', defaultMessage: 'Export to your calendar' }, copy: { id: 'event.copy', defaultMessage: 'Copy link to event' }, + bookmark: { id: 'status.bookmark', defaultMessage: 'Bookmark' }, + unbookmark: { id: 'status.unbookmark', defaultMessage: 'Remove bookmark' }, + adminAccount: { id: 'status.admin_account', defaultMessage: 'Moderate @{name}' }, + adminStatus: { id: 'status.admin_status', defaultMessage: 'Open this post in the moderation interface' }, + markStatusSensitive: { id: 'admin.statuses.actions.mark_status_sensitive', defaultMessage: 'Mark post sensitive' }, + markStatusNotSensitive: { id: 'admin.statuses.actions.mark_status_not_sensitive', defaultMessage: 'Mark post not sensitive' }, + deleteStatus: { id: 'admin.statuses.actions.delete_status', defaultMessage: 'Delete post' }, }); interface IEventHeader { @@ -33,13 +42,15 @@ const EventHeader: React.FC = ({ status }) => { const intl = useIntl(); const dispatch = useAppDispatch(); - const me = useAppSelector(state => state.me); + const ownAccount = useOwnAccount(); + const isStaff = ownAccount ? ownAccount.staff : false; + const isAdmin = ownAccount ? ownAccount.admin : false; if (!status || !status.event) { return ( <>
-
+
@@ -52,17 +63,51 @@ const EventHeader: React.FC = ({ status }) => { const banner = status.media_attachments?.find(({ description }) => description === 'Banner'); const handleHeaderClick: React.MouseEventHandler = (e) => { - e.preventDefault(); + e.stopPropagation(); const index = status.media_attachments!.findIndex(({ description }) => description === 'Banner'); dispatch(openModal('MEDIA', { media: status.media_attachments, index })); }; - const handleExportClick: React.MouseEventHandler = e => { + const handleExportClick = () => { dispatch(fetchEventIcs(status.id)).then((response) => { download(response, 'calendar.ics'); }).catch(() => {}); - e.preventDefault(); + }; + + const handleCopy = () => { + const { uri } = status; + const textarea = document.createElement('textarea'); + + textarea.textContent = uri; + textarea.style.position = 'fixed'; + + document.body.appendChild(textarea); + + try { + textarea.select(); + document.execCommand('copy'); + } catch { + // Do nothing + } finally { + document.body.removeChild(textarea); + } + }; + + const handleModerate = () => { + dispatch(openModal('ACCOUNT_MODERATION', { accountId: account.id })); + }; + + const handleModerateStatus = () => { + window.open(`/pleroma/admin/#/statuses/${status.id}/`, '_blank'); + }; + + const handleToggleStatusSensitivity = () => { + dispatch(toggleStatusSensitivityModal(intl, status.id, status.sensitive)); + }; + + const handleDeleteStatus = () => { + dispatch(deleteStatusModal(intl, status.id)); }; const menu: MenuType = [ @@ -71,12 +116,66 @@ const EventHeader: React.FC = ({ status }) => { action: handleExportClick, icon: require('@tabler/icons/calendar-plus.svg'), }, + { + text: intl.formatMessage(messages.copy), + action: handleCopy, + icon: require('@tabler/icons/link.svg'), + }, ]; + if (isStaff) { + menu.push(null); + + menu.push({ + text: intl.formatMessage(messages.adminAccount, { name: account.username }), + action: handleModerate, + icon: require('@tabler/icons/gavel.svg'), + }); + + if (isAdmin) { + menu.push({ + text: intl.formatMessage(messages.adminStatus), + action: handleModerateStatus, + icon: require('@tabler/icons/pencil.svg'), + }); + } + + menu.push({ + text: intl.formatMessage(status.sensitive === false ? messages.markStatusSensitive : messages.markStatusNotSensitive), + action: handleToggleStatusSensitivity, + icon: require('@tabler/icons/alert-triangle.svg'), + }); + + if (account.id !== ownAccount?.id) { + menu.push({ + text: intl.formatMessage(messages.deleteStatus), + action: handleDeleteStatus, + icon: require('@tabler/icons/trash.svg'), + destructive: true, + }); + } + } + + const handleManageClick: React.MouseEventHandler = e => { + e.stopPropagation(); + + dispatch(openModal('MANAGE_EVENT', { + statusId: status.id, + })); + }; + + const handleParticipantsClick: React.MouseEventHandler = e => { + e.stopPropagation(); + + dispatch(openModal('EVENT_PARTICIPANTS', { + statusId: status.id, + })); + }; + return ( <>
-
+
{banner && ( = ({ status }) => { })} - {account.id !== me && } + {account.id === ownAccount?.id ? ( + + ) : } - + = ({ status }) => { + + + + + + + + + {event.location && ( diff --git a/app/soapbox/features/event/event-discussion.tsx b/app/soapbox/features/event/event-discussion.tsx index f83d8728b..b8df6af57 100644 --- a/app/soapbox/features/event/event-discussion.tsx +++ b/app/soapbox/features/event/event-discussion.tsx @@ -22,8 +22,6 @@ import type { VirtuosoHandle } from 'react-virtuoso'; import type { RootState } from 'soapbox/store'; import type { Attachment as AttachmentEntity } from 'soapbox/types/entities'; -const getStatus = makeGetStatus(); - const getDescendantsIds = createSelector([ (_: RootState, statusId: string) => statusId, (state: RootState) => state.contexts.replies, @@ -66,8 +64,11 @@ interface IEventDiscussion { const EventDiscussion: React.FC = (props) => { const dispatch = useAppDispatch(); + const getStatus = useCallback(makeGetStatus(), []); const status = useAppSelector(state => getStatus(state, { id: props.params.statusId })); + const me = useAppSelector((state) => state.me); + const descendantsIds = useAppSelector(state => { let descendantsIds = ImmutableOrderedSet(); @@ -104,8 +105,8 @@ const EventDiscussion: React.FC = (props) => { }, [props.params.statusId]); useEffect(() => { - if (isLoaded) dispatch(eventDiscussionCompose(`reply:${props.params.statusId}`, status!)); - }, [isLoaded]); + if (isLoaded && me) dispatch(eventDiscussionCompose(`reply:${props.params.statusId}`, status!)); + }, [isLoaded, me]); const handleMoveUp = (id: string) => { const index = ImmutableList(descendantsIds).indexOf(id); @@ -208,9 +209,9 @@ const EventDiscussion: React.FC = (props) => { return ( -
+ {me &&
-
+
}
= ({ params }) => { const dispatch = useAppDispatch(); + const getStatus = useCallback(makeGetStatus(), []); + const status = useAppSelector(state => getStatus(state, { id: params.statusId })) as StatusEntity; const settings = useSettings(); @@ -38,10 +40,96 @@ const EventInformation: React.FC = ({ params }) => { setShowMedia(defaultMediaVisibility(status, displayMedia)); }, [params.statusId]); - const handleToggleMediaVisibility = (): void => { + const handleToggleMediaVisibility = () => { setShowMedia(!showMedia); }; + const handleShowMap: React.MouseEventHandler = (e) => { + e.stopPropagation(); + + dispatch(openModal('EVENT_MAP', { + statusId: status.id, + })); + }; + + const renderEventLocation = useCallback(() => { + const event = status?.event; + + return event?.location && ( + + + + + + + + {event.location.get('name')} +
+ {!!event.location.get('street')?.trim() && (<> + {event.location.get('street')} +
+ )} + {[event.location.get('postalCode'), event.location.get('locality'), event.location.get('country')].filter(text => text).join(', ')} + {event.location.get('latitude') && (<> +
+ + + + )} +
+
+
+ ); + }, [status]); + + const renderEventDate = useCallback(() => { + const event = status?.event; + + if (!event?.start_time) return null; + + return ( + + + + + + + + + {event.end_time && (<> + {' - '} + + )} + + + + ); + }, [status]); + + const renderLinks = useCallback(() => { + const links = status?.media_attachments.filter(({ pleroma }) => pleroma.get('mime_type') === 'text/html'); + + if (!links?.size) return null; + + + return ( + + + + + + {links.map(link => ( + + + + {(link.remote_url || link.url).replace(/^https?:\/\//, '')} + + + ))} + + ); + }, [status]); + if (!status && isLoaded) { return ( @@ -50,11 +138,16 @@ const EventInformation: React.FC = ({ params }) => { return ( - + + + + + + = ({ params }) => { showMedia={showMedia} onToggleVisibility={handleToggleMediaVisibility} /> + + {renderEventLocation()} + + {renderEventDate()} + + {renderLinks()} ); }; diff --git a/app/soapbox/features/ui/components/bundle_modal_error.tsx b/app/soapbox/features/ui/components/bundle_modal_error.tsx index 2945c442b..1a8ab1d5b 100644 --- a/app/soapbox/features/ui/components/bundle_modal_error.tsx +++ b/app/soapbox/features/ui/components/bundle_modal_error.tsx @@ -1,10 +1,10 @@ import React from 'react'; import { defineMessages, useIntl } from 'react-intl'; -import IconButton from 'soapbox/components/icon_button'; +import { Modal } from 'soapbox/components/ui'; const messages = defineMessages({ - error: { id: 'bundle_modal_error.message', defaultMessage: 'Something went wrong while loading this page.' }, + error: { id: 'bundle_modal_error.message', defaultMessage: 'Something went wrong while loading this modal.' }, retry: { id: 'bundle_modal_error.retry', defaultMessage: 'Try again' }, close: { id: 'bundle_modal_error.close', defaultMessage: 'Close' }, }); @@ -22,23 +22,13 @@ const BundleModalError: React.FC = ({ onRetry, onClose }) => }; return ( -
-
- - {intl.formatMessage(messages.error)} -
- -
-
- -
-
-
+ ); }; diff --git a/app/soapbox/features/ui/components/compose-button.tsx b/app/soapbox/features/ui/components/compose-button.tsx index bbe6467a3..2eff172a4 100644 --- a/app/soapbox/features/ui/components/compose-button.tsx +++ b/app/soapbox/features/ui/components/compose-button.tsx @@ -7,7 +7,7 @@ import { Button } from 'soapbox/components/ui'; const ComposeButton = () => { const dispatch = useDispatch(); - const onOpenCompose = () => dispatch(openModal('COMPOSE')); + const onOpenCompose = () => dispatch(openModal('CREATE_EVENT')); return (
diff --git a/app/soapbox/features/ui/components/modal_root.js b/app/soapbox/features/ui/components/modal_root.js index ab29e93ec..86cd868ac 100644 --- a/app/soapbox/features/ui/components/modal_root.js +++ b/app/soapbox/features/ui/components/modal_root.js @@ -35,6 +35,8 @@ import { CreateEventModal, JoinEventModal, AccountModerationModal, + EventMapModal, + EventParticipantsModal, } from 'soapbox/features/ui/util/async-components'; import BundleContainer from '../containers/bundle_container'; @@ -75,6 +77,8 @@ const MODAL_COMPONENTS = { 'CREATE_EVENT': CreateEventModal, 'JOIN_EVENT': JoinEventModal, 'ACCOUNT_MODERATION': AccountModerationModal, + 'EVENT_MAP': EventMapModal, + 'EVENT_PARTICIPANTS': EventParticipantsModal, }; export default class ModalRoot extends React.PureComponent { diff --git a/app/soapbox/features/ui/components/modals/event-map-modal.tsx b/app/soapbox/features/ui/components/modals/event-map-modal.tsx new file mode 100644 index 000000000..b9d3ca968 --- /dev/null +++ b/app/soapbox/features/ui/components/modals/event-map-modal.tsx @@ -0,0 +1,79 @@ +import L from 'leaflet'; +import React, { useCallback, useEffect, useRef } from 'react'; +import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; + +import { Button, Modal, Stack } from 'soapbox/components/ui'; +import { useAppSelector } from 'soapbox/hooks'; +import { makeGetStatus } from 'soapbox/selectors'; + +import type { Status as StatusEntity } from 'soapbox/types/entities'; + +import 'leaflet/dist/leaflet.css'; + +L.Icon.Default.mergeOptions({ + iconRetinaUrl: require('leaflet/dist/images/marker-icon-2x.png'), + iconUrl: require('leaflet/dist/images/marker-icon.png'), + shadowUrl: require('leaflet/dist/images/marker-shadow.png'), +}); + +interface IEventMapModal { + onClose: (type: string) => void, + statusId: string, +} + +const messages = defineMessages({ + osmAttribution: { id: 'event_map.osm_attribution', defaultMessage: '© OpenStreetMap Contributors' }, +}); + +const EventMapModal: React.FC = ({ onClose, statusId }) => { + const intl = useIntl(); + + const getStatus = useCallback(makeGetStatus(), []); + const status = useAppSelector(state => getStatus(state, { id: statusId })) as StatusEntity; + const location = status.event!.location!; + + const map = useRef(); + + useEffect(() => { + const latlng: [number, number] = [+location.get('latitude'), +location.get('longitude')]; + + map.current = L.map('event-map').setView(latlng, 15); + + L.marker(latlng, { + title: location.get('name'), + }).addTo(map.current); + + L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { + attribution: intl.formatMessage(messages.osmAttribution), + }).addTo(map.current); + + return () => { + map.current?.remove(); + }; + }, []); + + const onClickClose = () => { + onClose('EVENT_MAP'); + }; + + const onClickNavigate = () => { + window.open(`https://www.openstreetmap.org/directions?from=&to=${location.get('latitude')},${location.get('longitude')}#map=14/${location.get('latitude')}/${location.get('longitude')}`, '_blank'); + }; + + return ( + } + onClose={onClickClose} + width='2xl' + > + +
+ + + + ); +}; + +export default EventMapModal; diff --git a/app/soapbox/features/ui/components/modals/event-participants-modal.tsx b/app/soapbox/features/ui/components/modals/event-participants-modal.tsx new file mode 100644 index 000000000..bace52774 --- /dev/null +++ b/app/soapbox/features/ui/components/modals/event-participants-modal.tsx @@ -0,0 +1,59 @@ +import React, { useEffect } from 'react'; +import { FormattedMessage } from 'react-intl'; + +import { fetchEventParticipations } from 'soapbox/actions/events'; +import { Modal, Spinner, Stack } from 'soapbox/components/ui'; +import AccountContainer from 'soapbox/containers/account_container'; +import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; + +interface IEventParticipantsModal { + onClose: (type: string) => void, + statusId: string, +} + +const EventParticipantsModal: React.FC = ({ onClose, statusId }) => { + const dispatch = useAppDispatch(); + + const accountIds = useAppSelector((state) => state.user_lists.event_participations.get(statusId)?.items); + + const fetchData = () => { + dispatch(fetchEventParticipations(statusId)); + }; + + useEffect(() => { + fetchData(); + }, []); + + const onClickClose = () => { + onClose('EVENT_PARTICIPANTS'); + }; + + let body; + + if (!accountIds) { + body = ; + } else { + body = ( + + {accountIds.size > 0 ? ( + accountIds.map((id) => + , + ) + ) : ( + + )} + + ); + } + + return ( + } + onClose={onClickClose} + > + {body} + + ); +}; + +export default EventParticipantsModal; diff --git a/app/soapbox/features/ui/components/unauthorized_modal.tsx b/app/soapbox/features/ui/components/unauthorized_modal.tsx index ec6865b23..19c8408c6 100644 --- a/app/soapbox/features/ui/components/unauthorized_modal.tsx +++ b/app/soapbox/features/ui/components/unauthorized_modal.tsx @@ -15,7 +15,7 @@ const messages = defineMessages({ interface IUnauthorizedModal { /** Unauthorized action type. */ - action: 'FOLLOW' | 'REPLY' | 'REBLOG' | 'FAVOURITE' | 'POLL_VOTE', + action: 'FOLLOW' | 'REPLY' | 'REBLOG' | 'FAVOURITE' | 'POLL_VOTE' | 'JOIN', /** Close event handler. */ onClose: (modalType: string) => void, /** ActivityPub ID of the account OR status being acted upon. */ @@ -89,6 +89,9 @@ const UnauthorizedModal: React.FC = ({ action, onClose, acco } else if (action === 'POLL_VOTE') { header = ; button = ; + } else if (action === 'JOIN') { + header = ; + button = ; } return ( diff --git a/app/soapbox/features/ui/util/async-components.ts b/app/soapbox/features/ui/util/async-components.ts index 08668b662..4bf598fd7 100644 --- a/app/soapbox/features/ui/util/async-components.ts +++ b/app/soapbox/features/ui/util/async-components.ts @@ -525,3 +525,11 @@ export function EventInformation() { export function EventDiscussion() { return import(/* webpackChunkName: "features/event" */'../../event/event-discussion'); } + +export function EventMapModal() { + return import(/* webpackChunkName: "modals/event-map-modal" */'../components/modals/event-map-modal'); +} + +export function EventParticipantsModal() { + return import(/* webpackChunkName: "modals/event-participants-modal" */'../components/modals/event-participants-modal'); +} diff --git a/app/soapbox/locales/ast.json b/app/soapbox/locales/ast.json index cf907e74d..e8e18264b 100644 --- a/app/soapbox/locales/ast.json +++ b/app/soapbox/locales/ast.json @@ -161,7 +161,7 @@ "bundle_column_error.retry": "Try again", "bundle_column_error.title": "Network error", "bundle_modal_error.close": "Close", - "bundle_modal_error.message": "Something went wrong while loading this page.", + "bundle_modal_error.message": "Something went wrong while loading this modal.", "bundle_modal_error.retry": "Try again", "card.back.label": "Back", "chat_box.actions.send": "Send", diff --git a/app/soapbox/locales/bg.json b/app/soapbox/locales/bg.json index c470f3895..05c4b559e 100644 --- a/app/soapbox/locales/bg.json +++ b/app/soapbox/locales/bg.json @@ -161,7 +161,7 @@ "bundle_column_error.retry": "Опитай отново", "bundle_column_error.title": "Network error", "bundle_modal_error.close": "Close", - "bundle_modal_error.message": "Something went wrong while loading this page.", + "bundle_modal_error.message": "Something went wrong while loading this modal.", "bundle_modal_error.retry": "Try again", "card.back.label": "Back", "chat_box.actions.send": "Send", diff --git a/app/soapbox/locales/br.json b/app/soapbox/locales/br.json index 4008d5d24..217b27678 100644 --- a/app/soapbox/locales/br.json +++ b/app/soapbox/locales/br.json @@ -161,7 +161,7 @@ "bundle_column_error.retry": "Klask endro", "bundle_column_error.title": "Fazi rouedad", "bundle_modal_error.close": "Serriñ", - "bundle_modal_error.message": "Something went wrong while loading this page.", + "bundle_modal_error.message": "Something went wrong while loading this modal.", "bundle_modal_error.retry": "Klask endro", "card.back.label": "Back", "chat_box.actions.send": "Send", diff --git a/app/soapbox/locales/en.json b/app/soapbox/locales/en.json index 7ce350976..f6abee9f9 100644 --- a/app/soapbox/locales/en.json +++ b/app/soapbox/locales/en.json @@ -161,7 +161,7 @@ "bundle_column_error.retry": "Try again", "bundle_column_error.title": "Network error", "bundle_modal_error.close": "Close", - "bundle_modal_error.message": "Something went wrong while loading this page.", + "bundle_modal_error.message": "Something went wrong while loading this modal.", "bundle_modal_error.retry": "Try again", "card.back.label": "Back", "chat_box.actions.send": "Send", diff --git a/app/soapbox/locales/ga.json b/app/soapbox/locales/ga.json index 49a8e0491..ca4064534 100644 --- a/app/soapbox/locales/ga.json +++ b/app/soapbox/locales/ga.json @@ -161,7 +161,7 @@ "bundle_column_error.retry": "Try again", "bundle_column_error.title": "Network error", "bundle_modal_error.close": "Close", - "bundle_modal_error.message": "Something went wrong while loading this page.", + "bundle_modal_error.message": "Something went wrong while loading this modal.", "bundle_modal_error.retry": "Try again", "card.back.label": "Back", "chat_box.actions.send": "Send", diff --git a/app/soapbox/locales/hi.json b/app/soapbox/locales/hi.json index 8c03d6c75..a53d1cc79 100644 --- a/app/soapbox/locales/hi.json +++ b/app/soapbox/locales/hi.json @@ -161,7 +161,7 @@ "bundle_column_error.retry": "Try again", "bundle_column_error.title": "Network error", "bundle_modal_error.close": "Close", - "bundle_modal_error.message": "Something went wrong while loading this page.", + "bundle_modal_error.message": "Something went wrong while loading this modal.", "bundle_modal_error.retry": "Try again", "card.back.label": "Back", "chat_box.actions.send": "Send", diff --git a/app/soapbox/locales/hr.json b/app/soapbox/locales/hr.json index 35a0d7436..93c0c1d74 100644 --- a/app/soapbox/locales/hr.json +++ b/app/soapbox/locales/hr.json @@ -161,7 +161,7 @@ "bundle_column_error.retry": "Try again", "bundle_column_error.title": "Network error", "bundle_modal_error.close": "Close", - "bundle_modal_error.message": "Something went wrong while loading this page.", + "bundle_modal_error.message": "Something went wrong while loading this modal.", "bundle_modal_error.retry": "Try again", "card.back.label": "Back", "chat_box.actions.send": "Send", diff --git a/app/soapbox/locales/io.json b/app/soapbox/locales/io.json index 36a86bc2c..e2cf0d438 100644 --- a/app/soapbox/locales/io.json +++ b/app/soapbox/locales/io.json @@ -161,7 +161,7 @@ "bundle_column_error.retry": "Try again", "bundle_column_error.title": "Network error", "bundle_modal_error.close": "Close", - "bundle_modal_error.message": "Something went wrong while loading this page.", + "bundle_modal_error.message": "Something went wrong while loading this modal.", "bundle_modal_error.retry": "Try again", "card.back.label": "Back", "chat_box.actions.send": "Send", diff --git a/app/soapbox/locales/lt.json b/app/soapbox/locales/lt.json index 9fdc5d331..2794fe5f2 100644 --- a/app/soapbox/locales/lt.json +++ b/app/soapbox/locales/lt.json @@ -161,7 +161,7 @@ "bundle_column_error.retry": "Try again", "bundle_column_error.title": "Network error", "bundle_modal_error.close": "Close", - "bundle_modal_error.message": "Something went wrong while loading this page.", + "bundle_modal_error.message": "Something went wrong while loading this modal.", "bundle_modal_error.retry": "Try again", "card.back.label": "Back", "chat_box.actions.send": "Send", diff --git a/app/soapbox/locales/ms.json b/app/soapbox/locales/ms.json index 790f63a45..cb944094f 100644 --- a/app/soapbox/locales/ms.json +++ b/app/soapbox/locales/ms.json @@ -161,7 +161,7 @@ "bundle_column_error.retry": "Try again", "bundle_column_error.title": "Network error", "bundle_modal_error.close": "Close", - "bundle_modal_error.message": "Something went wrong while loading this page.", + "bundle_modal_error.message": "Something went wrong while loading this modal.", "bundle_modal_error.retry": "Try again", "card.back.label": "Back", "chat_box.actions.send": "Send", diff --git a/app/soapbox/reducers/statuses.ts b/app/soapbox/reducers/statuses.ts index 0d8b1c6ad..28662ee75 100644 --- a/app/soapbox/reducers/statuses.ts +++ b/app/soapbox/reducers/statuses.ts @@ -261,12 +261,12 @@ export default function statuses(state = initialState, action: AnyAction): State case TIMELINE_DELETE: return deleteStatus(state, action.id, action.references); case EVENT_JOIN_REQUEST: - return state.setIn([action.status.get('id'), 'event', 'join_state'], 'pending'); + return state.setIn([action.id, 'event', 'join_state'], 'pending'); case EVENT_JOIN_FAIL: case EVENT_LEAVE_REQUEST: - return state.setIn([action.status.get('id'), 'event', 'join_state'], null); + return state.setIn([action.id, 'event', 'join_state'], null); case EVENT_LEAVE_FAIL: - return state.setIn([action.status.get('id'), 'event', 'join_state'], action.previousState); + return state.setIn([action.id, 'event', 'join_state'], action.previousState); default: return state; } diff --git a/package.json b/package.json index 389bbb379..d44e12a99 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,7 @@ "@types/escape-html": "^1.0.1", "@types/http-link-header": "^1.0.3", "@types/jest": "^28.1.4", + "@types/leaflet": "^1.8.0", "@types/lodash": "^4.14.180", "@types/object-assign": "^4.0.30", "@types/object-fit-images": "^3.2.3", @@ -135,6 +136,7 @@ "intl-pluralrules": "^1.3.1", "is-nan": "^1.2.1", "jsdoc": "~3.6.7", + "leaflet": "^1.8.0", "libphonenumber-js": "^1.10.8", "line-awesome": "^1.3.0", "localforage": "^1.10.0", diff --git a/webpack/rules/assets.js b/webpack/rules/assets.js index e0a14c250..f3ba14b23 100644 --- a/webpack/rules/assets.js +++ b/webpack/rules/assets.js @@ -11,6 +11,7 @@ module.exports = [{ include: [ resolve('app', 'images'), resolve('node_modules', 'emoji-datasource'), + resolve('node_modules', 'leaflet'), ], generator: { filename: 'packs/images/[name]-[contenthash:8][ext]', diff --git a/yarn.lock b/yarn.lock index 69a7f05b9..725ccb4fd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2527,6 +2527,11 @@ dependencies: "@types/node" "*" +"@types/geojson@*": + version "7946.0.10" + resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.10.tgz#6dfbf5ea17142f7f9a043809f1cd4c448cb68249" + integrity sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA== + "@types/graceful-fs@^4.1.3": version "4.1.5" resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" @@ -2635,6 +2640,13 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= +"@types/leaflet@^1.8.0": + version "1.8.0" + resolved "https://registry.yarnpkg.com/@types/leaflet/-/leaflet-1.8.0.tgz#dc92d3e868fb6d5067b4b59fa08cd4441f84fabe" + integrity sha512-+sXFmiJTFdhaXXIGFlV5re9AdqtAODoXbGAvxx02e5SHXL3ir7ClP5J7pahO8VmzKY3dth4RUS1nf2BTT+DW1A== + dependencies: + "@types/geojson" "*" + "@types/lodash@^4.14.180": version "4.14.180" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.180.tgz#4ab7c9ddfc92ec4a887886483bc14c79fb380670" @@ -7823,6 +7835,11 @@ language-tags@^1.0.5: dependencies: language-subtag-registry "~0.3.2" +leaflet@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.8.0.tgz#4615db4a22a304e8e692cae9270b983b38a2055e" + integrity sha512-gwhMjFCQiYs3x/Sf+d49f10ERXaEFCPr+nVTryhAW8DWbMGqJqt9G4XuIaHmFW08zYvhgdzqXGr8AlW8v8dQkA== + leven@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2"