UNPKG

@atlaskit/renderer

Version:
371 lines (366 loc) • 14.7 kB
import _defineProperty from "@babel/runtime/helpers/defineProperty"; import _extends from "@babel/runtime/helpers/extends"; /** * @jsxRuntime classic * @jsx jsx * @jsxFrag */ import React, { PureComponent, Fragment, useEffect, useState, useMemo } from 'react'; /* eslint-disable @typescript-eslint/consistent-type-imports, @atlaskit/ui-styling-standard/use-compiled -- Ignored via go/DSP-18766; jsx required at runtime for @jsxRuntime classic */ import { jsx, css } from '@emotion/react'; import { AnalyticsContext } from '@atlaskit/analytics-next'; import { MEDIA_CONTEXT } from '@atlaskit/analytics-namespaced-context'; import { WithProviders } from '@atlaskit/editor-common/provider-factory'; import { MediaBorderGapFiller } from '@atlaskit/editor-common/ui'; import { fg } from '@atlaskit/platform-feature-flags'; import { MediaCard } from '../../../ui/MediaCard'; import { AnnotationMarkStates } from '@atlaskit/adf-schema'; import { hexToEditorBorderPaletteColor } from '@atlaskit/editor-palette'; import { getEventHandler } from '../../../utils'; import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, VIEW_METHOD } from '@atlaskit/editor-common/analytics'; import { MODE, PLATFORM } from '../../../analytics/events'; import AnnotationComponent from '../../marks/annotation'; import { CommentBadgeNext, ExternalImageBadge, MediaBadges } from '@atlaskit/editor-common/media-single'; import { useInlineCommentsFilter } from '../../../ui/annotations/hooks/use-inline-comments-filter'; import { useInlineCommentSubscriberContext } from '../../../ui/annotations/hooks/use-inline-comment-subscriber'; import { AnnotationUpdateEvent } from '@atlaskit/editor-common/types'; import { useAnnotationRangeState } from '../../../ui/annotations/contexts/AnnotationRangeContext'; const linkStyle = css({ position: 'absolute', background: 'transparent', top: 0, right: 0, bottom: 0, left: 0, cursor: 'pointer', // eslint-disable-next-line @atlaskit/ui-styling-standard/no-important-styles width: '100% !important', // eslint-disable-next-line @atlaskit/ui-styling-standard/no-important-styles height: '100% !important' }); const borderStyle = css({ position: 'absolute', // eslint-disable-next-line @atlaskit/ui-styling-standard/no-important-styles width: '100% !important', // eslint-disable-next-line @atlaskit/ui-styling-standard/no-important-styles height: '100% !important' }); const MediaBorder = ({ mark, children }) => { var _mark$attrs$color, _mark$attrs$size; if (!mark) { return jsx(Fragment, null, children); } const borderColor = (_mark$attrs$color = mark === null || mark === void 0 ? void 0 : mark.attrs.color) !== null && _mark$attrs$color !== void 0 ? _mark$attrs$color : ''; const borderWidth = (_mark$attrs$size = mark === null || mark === void 0 ? void 0 : mark.attrs.size) !== null && _mark$attrs$size !== void 0 ? _mark$attrs$size : 0; const paletteColorValue = hexToEditorBorderPaletteColor(borderColor) || borderColor; // [FEATURE FLAG: platform_editor_media_border_radius_fix] // Fixes border radius to properly match image with 8px radius // To clean up: keep only flag-on behavior ('8px') const borderRadius = fg('platform_editor_media_border_radius_fix') ? "var(--ds-radius-large, 8px)" : `${borderWidth}px`; // OLD BEHAVIOR (to be removed when flag is cleaned up) return jsx("div", { "data-mark-type": "border", "data-color": borderColor, "data-size": borderWidth, css: borderStyle, style: { // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766 borderRadius, boxShadow: `0 0 0 ${borderWidth}px ${paletteColorValue}` } }, jsx(MediaBorderGapFiller, { borderColor: borderColor }), children); }; const MediaLink = ({ mark, children, onClick }) => { if (!mark) { return jsx(Fragment, null, children); } const linkHref = mark === null || mark === void 0 ? void 0 : mark.attrs.href; return ( // eslint-disable-next-line @atlaskit/design-system/no-html-anchor jsx("a", { href: linkHref, rel: "noreferrer noopener", onClick: onClick, "data-block-link": linkHref, css: linkStyle }, children) ); }; const MediaAnnotation = ({ mark, children }) => { if (!mark) { return jsx(Fragment, null, children); } return jsx(AnnotationComponent, { id: mark.attrs.id, annotationType: mark.attrs.annotationType // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , dataAttributes: { 'data-renderer-mark': true, 'data-block-mark': true } // This should be fine being empty [] since the serializer serializeFragmentChild getMarkProps call always passes // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , annotationParentIds: [], allowAnnotations: true, useBlockLevel: true }, children); }; const MediaAnnotations = ({ marks = [], children }) => { // Early Exit if (marks.length === 0) { return jsx(Fragment, null, children); } // Recursive marks const currentMark = marks[0]; const otherMarks = marks.slice(1); return jsx(Fragment, null, jsx(MediaAnnotation, { key: currentMark.attrs.id, mark: currentMark }, otherMarks.length ? jsx(MediaAnnotations, { marks: otherMarks }, children) : jsx(Fragment, null, children))); }; const CommentBadgeWrapper = ({ marks, mediaSingleElement, isDrafting = false, ...rest }) => { var _marks$map; const [status, setStatus] = useState('default'); const [entered, setEntered] = useState(false); const updateSubscriber = useInlineCommentSubscriberContext(); const activeParentIds = useInlineCommentsFilter({ annotationIds: (_marks$map = marks === null || marks === void 0 ? void 0 : marks.map(mark => mark.attrs.id)) !== null && _marks$map !== void 0 ? _marks$map : [''], filter: { state: AnnotationMarkStates.ACTIVE } }); useEffect(() => { const observer = new MutationObserver(mutationList => { mutationList.forEach(mutation => { const parentNode = mutation.target.parentNode; if (mutation.attributeName === 'data-has-focus') { const isMediaCaption = parentNode === null || parentNode === void 0 ? void 0 : parentNode.closest('[data-media-caption="true"]'); const elementHasFocus = (parentNode === null || parentNode === void 0 ? void 0 : parentNode.querySelector('[data-has-focus="true"]')) && !isMediaCaption; elementHasFocus ? setStatus('active') : setStatus('default'); } }); }); if (mediaSingleElement) { observer.observe(mediaSingleElement, { attributes: true, subtree: true, attributeFilter: ['data-has-focus'] }); } return () => { observer.disconnect(); }; }, [mediaSingleElement, setStatus]); if (!isDrafting && !activeParentIds.length) { return null; } const onClick = e => { e.preventDefault(); if (updateSubscriber) { updateSubscriber.emit(AnnotationUpdateEvent.ON_ANNOTATION_CLICK, { annotationIds: activeParentIds, // Ignored via go/ees005 // eslint-disable-next-line @atlaskit/editor/no-as-casting eventTarget: e.target, // use mediaSingle here to align with annotation viewed event dispatched in editor eventTargetType: 'mediaSingle', viewMethod: VIEW_METHOD.BADGE }); } }; return jsx(CommentBadgeNext // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , _extends({ onMouseEnter: () => setEntered(true) // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , onMouseLeave: () => setEntered(false), status: entered ? 'entered' : status, onClick: onClick, mediaSingleElement: mediaSingleElement // Ignored via go/ees005 // eslint-disable-next-line react/jsx-props-no-spreading }, rest)); }; // Ignored via go/ees005 // eslint-disable-next-line @repo/internal/react/no-class-components class Media extends PureComponent { constructor(props) { super(props); _defineProperty(this, "renderCard", (providers = {}) => { const { contextIdentifierProvider } = providers; const { allowAltTextOnImages, alt, featureFlags, shouldOpenMediaViewer: allowMediaViewer, enableDownloadButton, ssr, width, height, mediaSingleElement, isDrafting = false } = this.props; const annotationMarks = this.props.isAnnotationMark ? this.props.marks.filter(this.props.isAnnotationMark) : undefined; const borderMark = this.props.marks.find(this.props.isBorderMark); const linkMark = this.props.marks.find(this.props.isLinkMark); const linkHref = linkMark === null || linkMark === void 0 ? void 0 : linkMark.attrs.href; const eventHandlers = linkHref ? undefined : this.props.eventHandlers; const shouldOpenMediaViewer = !linkHref && allowMediaViewer; const isInPageInclude = mediaSingleElement === null || mediaSingleElement === void 0 ? void 0 : mediaSingleElement.closest('[data-node-type="include"]'); const isIncludeExcerpt = !!(mediaSingleElement !== null && mediaSingleElement !== void 0 && mediaSingleElement.closest('.ak-excerpt-include')); const showCommentBadge = !!annotationMarks && !isInPageInclude && !isIncludeExcerpt; return jsx(MediaLink, { mark: linkMark, onClick: this.handleMediaLinkClickFn }, jsx(MediaAnnotations, { marks: annotationMarks }, jsx(MediaBorder, { mark: borderMark }, jsx(AnalyticsContext // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , { data: { [MEDIA_CONTEXT]: { border: !!borderMark } } }, jsx(MediaBadges, { mediaElement: mediaSingleElement, mediaWidth: width, mediaHeight: height, useMinimumZIndex: true }, ({ visible }) => jsx(React.Fragment, null, visible && jsx(ExternalImageBadge, { type: this.props.type, url: this.props.type === 'external' ? this.props.url : undefined }), showCommentBadge && jsx(CommentBadgeWrapper, { marks: annotationMarks, mediaSingleElement: mediaSingleElement, isDrafting: isDrafting }))), jsx(MediaCard, _extends({ contextIdentifierProvider: contextIdentifierProvider // Ignored via go/ees005 // eslint-disable-next-line react/jsx-props-no-spreading }, this.props, { shouldOpenMediaViewer: shouldOpenMediaViewer, eventHandlers: eventHandlers, alt: allowAltTextOnImages ? alt : undefined, featureFlags: featureFlags, shouldEnableDownloadButton: enableDownloadButton, ssr: ssr })))))); }); _defineProperty(this, "handleMediaLinkClick", event => { const { fireAnalyticsEvent, isLinkMark, marks } = this.props; if (fireAnalyticsEvent) { fireAnalyticsEvent({ action: ACTION.VISITED, actionSubject: ACTION_SUBJECT.MEDIA, actionSubjectId: ACTION_SUBJECT_ID.LINK, eventType: EVENT_TYPE.TRACK, attributes: { platform: PLATFORM.WEB, mode: MODE.RENDERER } }); } const linkMark = this.props.marks.find(this.props.isLinkMark); const linkHref = linkMark === null || linkMark === void 0 ? void 0 : linkMark.attrs.href; const handler = getEventHandler(this.props.eventHandlers, 'link'); if (handler) { const linkMark = marks.find(isLinkMark); handler(event, linkMark && linkHref); } }); this.handleMediaLinkClickFn = this.handleMediaLinkClick.bind(this); } render() { const { providers } = this.props; if (!providers) { return this.renderCard(); } return jsx(WithProviders // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , { providers: ['mediaProvider', 'contextIdentifierProvider'], providerFactory: providers, renderNode: this.renderCard }); } } const MediaWithDraftAnnotation = props => { const { hoverDraftDocumentPosition: draftPosition } = useAnnotationRangeState(); const { dataAttributes } = props; const pos = dataAttributes && dataAttributes['data-renderer-start-pos']; const [position, setPosition] = useState(); const [shouldApplyDraftAnnotation, setShouldApplyDraftAnnotation] = useState(false); useEffect(() => { var _draftPosition$from; if (pos === undefined) { return; } const posToCheck = ((_draftPosition$from = draftPosition === null || draftPosition === void 0 ? void 0 : draftPosition.from) !== null && _draftPosition$from !== void 0 ? _draftPosition$from : 0) + 1; if (draftPosition !== null && posToCheck === pos) { // eslint-disable-next-line @atlassian/perf-linting/no-chain-state-updates -- Ignored via go/ees017 (to be fixed) setShouldApplyDraftAnnotation(true); // eslint-disable-next-line @atlassian/perf-linting/no-chain-state-updates -- Ignored via go/ees017 (to be fixed) setPosition(posToCheck); } else if (draftPosition === null && shouldApplyDraftAnnotation) { // eslint-disable-next-line @atlassian/perf-linting/no-chain-state-updates -- Ignored via go/ees017 (to be fixed) setShouldApplyDraftAnnotation(false); // eslint-disable-next-line @atlassian/perf-linting/no-chain-state-updates -- Ignored via go/ees017 (to be fixed) setPosition(undefined); } }, [draftPosition, pos, shouldApplyDraftAnnotation]); const applyDraftAnnotation = props.allowAnnotationsDraftMode && shouldApplyDraftAnnotation && position !== undefined; const dataAttributesWithDraftAnnotation = useMemo(() => applyDraftAnnotation ? { ...dataAttributes, 'data-annotation-draft-mark': true, 'data-renderer-mark': true } : dataAttributes, [applyDraftAnnotation, dataAttributes]); return jsx(Media // Ignored via go/ees005 // eslint-disable-next-line react/jsx-props-no-spreading , _extends({}, props, { dataAttributes: dataAttributesWithDraftAnnotation, isDrafting: shouldApplyDraftAnnotation })); }; export default MediaWithDraftAnnotation;