diff --git a/.changeset/plenty-rice-cheer.md b/.changeset/plenty-rice-cheer.md new file mode 100644 index 00000000..0583a1f5 --- /dev/null +++ b/.changeset/plenty-rice-cheer.md @@ -0,0 +1,6 @@ +--- +"@pantheon-systems/pcc-react-sdk": patch +--- + +The injected preview bar is deprecated in favor of the integrated preview bar in +the dashboard preview interface diff --git a/packages/react-sdk/package.json b/packages/react-sdk/package.json index 4030a628..7b841a13 100644 --- a/packages/react-sdk/package.json +++ b/packages/react-sdk/package.json @@ -103,12 +103,8 @@ "dependencies": { "@apollo/client": "^3.10.3", "@pantheon-systems/pcc-sdk-core": "workspace:*", - "framer-motion": "^10.18.0", - "goober": "^2.1.14", "graphql": "^16.8.1", "lodash": "^4.17.21", - "query-string": "^8.2.0", - "react-laag": "^2.0.5", "react-markdown": "^8.0.7", "rehype-raw": "^6.1.1", "remark-heading-id": "^1.0.1", diff --git a/packages/react-sdk/src/components/ArticleRenderer/index.tsx b/packages/react-sdk/src/components/ArticleRenderer/index.tsx index b5df3c97..9da2444f 100644 --- a/packages/react-sdk/src/components/ArticleRenderer/index.tsx +++ b/packages/react-sdk/src/components/ArticleRenderer/index.tsx @@ -6,9 +6,7 @@ import { } from "@pantheon-systems/pcc-sdk-core/types"; import { Element } from "hast"; import React, { useEffect } from "react"; -import { createPortal } from "react-dom"; import { getTextContent } from "../../utils/react-element"; -import { PreviewBar, PreviewBarExternalProps } from "../Preview/Preview"; import MarkdownRenderer from "./Markdown"; import PantheonTreeRenderer from "./PantheonTreeRenderer"; import PantheonTreeV2Renderer from "./PantheonTreeV2Renderer"; @@ -52,7 +50,6 @@ interface Props { content: string, ) => React.ReactNode; smartComponentMap?: SmartComponentMap; - previewBarProps?: PreviewBarExternalProps; componentMap?: ComponentMap; renderBody?: (bodyElement: React.ReactElement) => React.ReactNode; __experimentalFlags?: { @@ -63,22 +60,6 @@ interface Props { }; } -function getOrCreatePortalTarget( - targetOverride: globalThis.Element | null | undefined, -) { - const pccGeneratedPortalTargetKey = "__pcc-portal-target__"; - let portalTarget = - targetOverride || document.getElementById(pccGeneratedPortalTargetKey); - - if (!portalTarget) { - portalTarget = document.createElement("div"); - portalTarget.id = pccGeneratedPortalTargetKey; - document.body.prepend(portalTarget); - } - - return portalTarget; -} - const ArticleRenderer = ({ article, bodyClassName, @@ -86,13 +67,10 @@ const ArticleRenderer = ({ headerClassName, renderTitle, smartComponentMap, - previewBarProps, componentMap, renderBody, __experimentalFlags, }: Props) => { - const [renderCSR, setRenderCSR] = React.useState(false); - useEffect(() => { if (__experimentalFlags?.useUnintrusiveTitleRendering !== true) { console.warn( @@ -101,33 +79,15 @@ const ArticleRenderer = ({ } }, [__experimentalFlags]); - useEffect(() => { - setRenderCSR(true); - }, []); - if (!article?.content) { return null; } - const portalTarget = - renderCSR && typeof document !== "undefined" - ? getOrCreatePortalTarget(previewBarProps?.portalTarget) - : null; const contentType = article?.contentType; if (contentType === "TEXT_MARKDOWN") { return (
- {renderCSR && - article != null && - portalTarget != null && - article.publishingLevel === "REALTIME" - ? createPortal( - , - portalTarget, - ) - : null} - {article?.content ? ( - {renderCSR && - article != null && - portalTarget != null && - article.publishingLevel === "REALTIME" - ? createPortal( - , - portalTarget, - ) - : null} - {titleElement != null ? (
{renderTitle diff --git a/packages/react-sdk/src/components/Preview/LivePreviewIndicator.tsx b/packages/react-sdk/src/components/Preview/LivePreviewIndicator.tsx deleted file mode 100644 index a92a991b..00000000 --- a/packages/react-sdk/src/components/Preview/LivePreviewIndicator.tsx +++ /dev/null @@ -1,106 +0,0 @@ -/* eslint-disable jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */ -import { setup, styled } from "goober"; -import React, { ComponentProps, useState } from "react"; -import { Arrow, useLayer } from "react-laag"; -import { IconDot } from "../Icons/IconDot"; -import { IconInfo } from "../Icons/IconInfo"; - -interface Props { - isLive: boolean; -} - -setup(React.createElement); - -export const LivePreviewIndicator = ({ isLive }: Props) => { - const [isOpen, setIsOpen] = useState(false); - - const { renderLayer, triggerProps, layerProps, arrowProps } = useLayer({ - isOpen, - triggerOffset: 24, - placement: "bottom-center", - onOutsideClick: () => setIsOpen(false), - }); - - return ( - - - {isLive ? "Page preview: On" : "Page preview: Off"} - {!isLive ? ( - setIsOpen(!isOpen)} {...triggerProps}> - - - ) : null} - {isOpen && !isLive - ? renderLayer( -
-
- - This preview page is no longer connected to the document - (updates to the document will not be displayed until this is - reconnected). - -
-
- - To reconnect, navigate to the document and select the - 'Preview' button in the Content Cloud add-on. - -
- )} - size={9} - roundness={0} - backgroundColor="#23232D" - /> -
, - ) - : null} -
- ); -}; - -const Container = styled("div")` - border: 1px solid #cfcfd3; - font-weight: 600; - display: flex; - align-items: center; - border-radius: 100px; - color: #6d6d78; - padding: 0 8px; - font-size: 12px; - flex-direction: row; - width: fit-content; - height: 24px; - - > span { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - margin-left: 8px; - } -`; - -const InfoButton = styled("button", React.forwardRef)` - margin-left: 8px; -`; diff --git a/packages/react-sdk/src/components/Preview/Preview.tsx b/packages/react-sdk/src/components/Preview/Preview.tsx deleted file mode 100644 index 9438895e..00000000 --- a/packages/react-sdk/src/components/Preview/Preview.tsx +++ /dev/null @@ -1,240 +0,0 @@ -import "../../index.css"; -import { Article } from "@pantheon-systems/pcc-sdk-core/types"; -import { AnimatePresence, motion } from "framer-motion"; -import { setup, styled } from "goober"; -import queryString from "query-string"; -import React, { useEffect } from "react"; -import { getCookie } from "../../utils/cookies"; -import { IconDocs } from "../Icons/IconDocs"; -import { IconExport } from "../Icons/IconExport"; -import { IconShare } from "../Icons/IconShare"; -import { LivePreviewIndicator } from "./LivePreviewIndicator"; - -setup(React.createElement); - -export interface PreviewBarExternalProps { - previewBarOverride?: React.ReactElement | undefined | null; - openedPreviewBarProps?: React.HTMLAttributes; - portalTarget?: globalThis.Element | null | undefined; -} - -interface PreviewBarInternalProps { - article: Article; -} - -const pccGrant = getCookie("PCC-GRANT"); -const maxDocTitleLength = 51; - -export const PreviewBar = ({ - article, - previewBarOverride, - openedPreviewBarProps, -}: PreviewBarInternalProps & PreviewBarExternalProps) => { - const [isHidden, setIsHidden] = React.useState(true); - const [isLive, setIsLive] = React.useState(false); - const [hasCopied, setHasCopied] = React.useState(false); - const [copyResetTimeoutId, setCopyResetTimeoutId] = - React.useState(null); - - const truncatedDocTitle = - article?.title && article?.title?.length >= maxDocTitleLength - ? `${article?.title.substring(0, maxDocTitleLength)}...` - : article?.title; - - useEffect(() => { - // If no preview is active, then we can leave isLive as the default false. - if (!article.previewActiveUntil) return; - - const livePreviewTimeRemaining = article.previewActiveUntil - Date.now(); - if (livePreviewTimeRemaining >= 100) { - setIsLive(true); - setTimeout(() => { - setIsLive(false); - }, livePreviewTimeRemaining); - } - }, [article]); - - React.useEffect(() => { - if (typeof window !== "undefined" && typeof location !== "undefined") { - const parsed = queryString.parse(location.search); - - if (parsed.publishingLevel?.toString().toLowerCase() === "realtime") { - setIsHidden(false); - } - } - }, []); - - if (isHidden) { - return null; - } - - if (previewBarOverride != null) { - return React.cloneElement(previewBarOverride, { - article, - }); - } - - return ( - - - - - - {truncatedDocTitle} - - -
- - - - - - { - if (copyResetTimeoutId) { - clearTimeout(copyResetTimeoutId); - setCopyResetTimeoutId(null); - } - - const parsedUrl = queryString.parseUrl( - window.location.href, - { - parseFragmentIdentifier: true, - }, - ); - - const query = { - ...(parsedUrl.query || {}), - pccGrant, - }; - - navigator.clipboard.writeText( - `${parsedUrl.url}?${queryString.stringify(query)}${ - parsedUrl.fragmentIdentifier - ? `#${parsedUrl.fragmentIdentifier}` - : "" - }`, - ); - setHasCopied(true); - - // Reset the copied state after 2 seconds - const timeoutId = setTimeout(() => { - setHasCopied(false); - }, 2000); - setCopyResetTimeoutId(timeoutId); - }} - > - - {hasCopied ? "Copied URL" : "Share preview URL"} - - - -
-
-
-
- ); -}; - -const Wrapper = styled("div")` - font-family: Poppins, sans-serif; - z-index: 5; - position: relative; - top: 0; - width: 100%; - transition: all 0.2s ease-in-out; - display: flex; - justify-content: flex-end; - - box-shadow: - 0px 0px 0px 1px rgba(0, 0, 0, 0.08), - 0px 8px 8px -8px rgba(0, 0, 0, 0.04); - padding: 15px 0; -`; - -const Container = styled(motion.div)` - padding: 32px; - padding-block: 8px; - background: white; - display: grid; - gap: 1em; - width: 100%; - - @media (min-width: 768px) { - padding-block: 0; - grid-auto-flow: column; - grid-template-columns: 0.45fr 0.55fr; - } -`; - -const TitleSection = styled("a")` - display: flex; - flex-direction: row; - column-gap: 10px; - align-items: center; - font-size: 0.875rem; - font-weight: 600; - color: #23232d; - min-width: 0; - - > span { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - - > svg { - flex-shrink: 0; - } -`; - -const LivePreviewIndicatorContainer = styled("div")` - display: flex; - justify-content: center; - align-items: center; -`; - -const EndBlock = styled("div")` - display: flex; - flex-direction: row; - column-gap: 4px; - align-items: center; - justify-content: flex-end; -`; - -const CopyUrlButtonContainer = styled("div")` - height: 32px; -`; - -const CopyUrlButton = styled("button")` - height: 100%; - padding: 0 13px; - background-color: #3017a1; - color: white; - border-radius: 3px; - font-size: 0.875rem; - font-weight: 600; - column-gap: 5px; - - display: flex; - align-items: center; - - &:hover { - opacity: 0.8; - } -`; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ad34f926..e8b1c2a8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -395,24 +395,12 @@ importers: '@pantheon-systems/pcc-sdk-core': specifier: workspace:* version: link:../core - framer-motion: - specifier: ^10.18.0 - version: 10.18.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - goober: - specifier: ^2.1.14 - version: 2.1.14(csstype@3.1.3) graphql: specifier: ^16.8.1 version: 16.8.1 lodash: specifier: ^4.17.21 version: 4.17.21 - query-string: - specifier: ^8.2.0 - version: 8.2.0 - react-laag: - specifier: ^2.0.5 - version: 2.0.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-markdown: specifier: ^8.0.7 version: 8.0.7(@types/react@18.3.3)(react@18.3.1) @@ -2287,12 +2275,6 @@ packages: '@emnapi/runtime@1.2.0': resolution: {integrity: sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ==} - '@emotion/is-prop-valid@0.8.8': - resolution: {integrity: sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==} - - '@emotion/memoize@0.7.4': - resolution: {integrity: sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==} - '@emotion/use-insertion-effect-with-fallbacks@1.0.1': resolution: {integrity: sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==} peerDependencies: @@ -6148,6 +6130,7 @@ packages: acorn-import-assertions@1.9.0: resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==} + deprecated: package has been renamed to acorn-import-attributes peerDependencies: acorn: ^8 @@ -8606,17 +8589,6 @@ packages: fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} - framer-motion@10.18.0: - resolution: {integrity: sha512-oGlDh1Q1XqYPksuTD/usb0I70hq95OUzmL9+6Zd+Hs4XV0oaISBa/UUMSjYiq6m8EUF32132mOJ8xVZS+I0S6w==} - peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - peerDependenciesMeta: - react: - optional: true - react-dom: - optional: true - fresh@0.5.2: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} engines: {node: '>= 0.6'} @@ -8941,11 +8913,6 @@ packages: resolution: {integrity: sha512-jOMLD2Z7MAhyG8aJpNOpmziMOP4rPLcc95oQPKXBazW82z+CEgPFBQvEpRUa1KeIMUJo4Wsm+q6uzO/Q/4BksQ==} engines: {node: '>=18'} - goober@2.1.14: - resolution: {integrity: sha512-4UpC0NdGyAFqLNPnhCT2iHpza2q+RAY3GV85a/mRPdzyPQMsj0KmMMuetdIkzWRbJ+Hgau1EZztq8ImmiMGhsg==} - peerDependencies: - csstype: ^3.0.10 - google-auth-library@9.13.0: resolution: {integrity: sha512-p9Y03Uzp/Igcs36zAaB0XTSwZ8Y0/tpYiz5KIde5By+H9DCVUSYtDWZu6aFXsWTqENMb8BD/pDT3hR8NVrPkfA==} engines: {node: '>=14'} @@ -12315,12 +12282,6 @@ packages: react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} - react-laag@2.0.5: - resolution: {integrity: sha512-RCvublJhdcgGRHU1wMYJ8kRtnYsKUgYusLvVhMuftg65POnnOB4+fwXvnETm6adc0cMnc1spujlrK6bGIz6aug==} - peerDependencies: - react: ^16.0.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 - react-loading-skeleton@3.4.0: resolution: {integrity: sha512-1oJEBc9+wn7BbkQQk7YodlYEIjgeR+GrRjD+QXkVjwZN7LGIcAFHrx4NhT7UHGBxNY1+zax3c+Fo6XQM4R7CgA==} peerDependencies: @@ -13531,9 +13492,6 @@ packages: tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} - tiny-warning@1.0.3: - resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} - tinybench@2.6.0: resolution: {integrity: sha512-N8hW3PG/3aOoZAN5V/NSAEDz0ZixDSSt5b/a05iqtpgfLWMSVuCo7w0k2vVvEjdrIoeGqZzweX2WlyioNIHchA==} @@ -17160,14 +17118,6 @@ snapshots: tslib: 2.6.2 optional: true - '@emotion/is-prop-valid@0.8.8': - dependencies: - '@emotion/memoize': 0.7.4 - optional: true - - '@emotion/memoize@0.7.4': - optional: true - '@emotion/use-insertion-effect-with-fallbacks@1.0.1(react@18.3.1)': dependencies: react: 18.3.1 @@ -25709,14 +25659,6 @@ snapshots: fraction.js@4.3.7: {} - framer-motion@10.18.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - tslib: 2.6.2 - optionalDependencies: - '@emotion/is-prop-valid': 0.8.8 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - fresh@0.5.2: {} fs-constants@1.0.0: {} @@ -26714,10 +26656,6 @@ snapshots: slash: 5.1.0 unicorn-magic: 0.1.0 - goober@2.1.14(csstype@3.1.3): - dependencies: - csstype: 3.1.3 - google-auth-library@9.13.0(encoding@0.1.13): dependencies: base64-js: 1.5.1 @@ -31266,12 +31204,6 @@ snapshots: react-is@18.3.1: {} - react-laag@2.0.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - tiny-warning: 1.0.3 - react-loading-skeleton@3.4.0(react@18.3.1): dependencies: react: 18.3.1 @@ -32808,8 +32740,6 @@ snapshots: tiny-invariant@1.3.3: {} - tiny-warning@1.0.3: {} - tinybench@2.6.0: {} tinypool@0.8.2: {}