diff --git a/package-lock.json b/package-lock.json index 284851e..03656b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,7 @@ "eslint-plugin-jsx-a11y": "^6.7.1", "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.6.0", + "prettier": "^3.2.5", "typescript": "^5.1.6", "vite": "^5.1.0", "vite-tsconfig-paths": "^4.2.1" @@ -2619,6 +2620,21 @@ "node": ">=6" } }, + "node_modules/@remix-run/dev/node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/@remix-run/dev/node_modules/tsconfig-paths": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", @@ -11013,15 +11029,15 @@ } }, "node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", "dev": true, "bin": { - "prettier": "bin-prettier.js" + "prettier": "bin/prettier.cjs" }, "engines": { - "node": ">=10.13.0" + "node": ">=14" }, "funding": { "url": "https://github.com/prettier/prettier?sponsor=1" diff --git a/package.json b/package.json index 32adb5a..273b658 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "eslint-plugin-jsx-a11y": "^6.7.1", "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.6.0", + "prettier": "^3.2.5", "typescript": "^5.1.6", "vite": "^5.1.0", "vite-tsconfig-paths": "^4.2.1" diff --git a/remix/.eslintrc.cjs b/remix/.eslintrc.cjs index c84b96d..a13e50b 100644 --- a/remix/.eslintrc.cjs +++ b/remix/.eslintrc.cjs @@ -8,8 +8,8 @@ module.exports = { root: true, parserOptions: { - ecmaVersion: 'latest', - sourceType: 'module', + ecmaVersion: "latest", + sourceType: "module", ecmaFeatures: { jsx: true, }, @@ -19,38 +19,38 @@ module.exports = { commonjs: true, es6: true, }, - ignorePatterns: ['build/**/*', 'node_modules/**/*', 'public/**/*'], + ignorePatterns: ["build/**/*", "node_modules/**/*", "public/**/*"], // Base config - extends: ['eslint:recommended'], + extends: ["eslint:recommended"], overrides: [ // React { - files: ['**/*.{js,jsx,ts,tsx}'], - plugins: ['react', 'jsx-a11y'], + files: ["**/*.{js,jsx,ts,tsx}"], + plugins: ["react", "jsx-a11y"], rules: { - 'react/jsx-uses-vars': 'error', - 'react/jsx-uses-react': 'error', + "react/jsx-uses-vars": "error", + "react/jsx-uses-react": "error", }, extends: [ - 'plugin:react/recommended', - 'plugin:react/jsx-runtime', - 'plugin:react-hooks/recommended', - 'plugin:jsx-a11y/recommended', + "plugin:react/recommended", + "plugin:react/jsx-runtime", + "plugin:react-hooks/recommended", + "plugin:jsx-a11y/recommended", ], settings: { react: { - version: 'detect', + version: "detect", }, - formComponents: ['Form'], + formComponents: ["Form"], linkComponents: [ - { name: 'Link', linkAttribute: 'to' }, - { name: 'NavLink', linkAttribute: 'to' }, + { name: "Link", linkAttribute: "to" }, + { name: "NavLink", linkAttribute: "to" }, ], - 'import/resolver': { + "import/resolver": { typescript: { - project: './remix/tsconfig.json', + project: "./remix/tsconfig.json", }, }, }, @@ -58,14 +58,14 @@ module.exports = { // Typescript { - files: ['**/*.{ts,tsx}'], - plugins: ['@typescript-eslint', 'import'], - parser: '@typescript-eslint/parser', + files: ["**/*.{ts,tsx}"], + plugins: ["@typescript-eslint", "import"], + parser: "@typescript-eslint/parser", settings: { - 'import/internal-regex': '^~/', - 'import/resolver': { + "import/internal-regex": "^~/", + "import/resolver": { node: { - extensions: ['.ts', '.tsx'], + extensions: [".ts", ".tsx"], }, typescript: { alwaysTryTypes: true, @@ -73,18 +73,18 @@ module.exports = { }, }, extends: [ - 'plugin:@typescript-eslint/recommended', - 'plugin:import/recommended', - 'plugin:import/typescript', + "plugin:@typescript-eslint/recommended", + "plugin:import/recommended", + "plugin:import/typescript", ], }, // Node { - files: ['.eslintrc.cjs'], + files: [".eslintrc.cjs"], env: { node: true, }, }, ], -}; +} diff --git a/remix/.prettierignore b/remix/.prettierignore new file mode 100644 index 0000000..00d3c64 --- /dev/null +++ b/remix/.prettierignore @@ -0,0 +1,11 @@ +# Directories +build +public + +# Files +.eslintrc.cjs +.prettierignore +package.json +package-lock.json +prettier.config.cjs +tsconfig.json diff --git a/remix/app/components/ConfigForm.tsx b/remix/app/components/ConfigForm.tsx index 02c9a28..fa81f15 100644 --- a/remix/app/components/ConfigForm.tsx +++ b/remix/app/components/ConfigForm.tsx @@ -4,22 +4,22 @@ import { Help, Line, TextInput, -} from "@contentstack/venus-components"; -import { ChangeEvent, useState } from "react"; -import { ImageVaultDAMConfig } from "~/utils/imagevault"; +} from "@contentstack/venus-components" +import { ChangeEvent, useState } from "react" +import { ImageVaultDAMConfig } from "~/utils/imagevault" -import type { IInstallationData } from "@contentstack/app-sdk/dist/src/types"; +import type { IInstallationData } from "@contentstack/app-sdk/dist/src/types" export type ConfigFormProps = { - values: Partial; - setInstallationData: (data: IInstallationData) => void; -}; + values: Partial + setInstallationData: (data: IInstallationData) => void +} export default function ConfigForm({ values, setInstallationData, }: ConfigFormProps) { - const [finalValues, setFinalValues] = useState(values); + const [finalValues, setFinalValues] = useState(values) return (
@@ -37,7 +37,7 @@ export default function ConfigForm({ setFinalValues((prev) => ({ ...prev, baseUrl: evt.target.value, - })); + })) setInstallationData({ configuration: { @@ -45,7 +45,7 @@ export default function ConfigForm({ baseUrl: evt.target.value, }, serverConfiguration: {}, - }); + }) }} required={true} willBlurOnEsc={true} @@ -76,7 +76,7 @@ export default function ConfigForm({ setFinalValues((prev) => ({ ...prev, formatId: evt.target.value, - })); + })) setInstallationData({ configuration: { @@ -84,7 +84,7 @@ export default function ConfigForm({ formatId: evt.target.value, }, serverConfiguration: {}, - }); + }) }} /> @@ -109,7 +109,7 @@ export default function ConfigForm({ setFinalValues((prev) => ({ ...prev, imageVaultUrl: evt.target.value, - })); + })) setInstallationData({ configuration: { @@ -117,10 +117,10 @@ export default function ConfigForm({ imageVaultUrl: evt.target.value, }, serverConfiguration: {}, - }); + }) }} />
- ); + ) } diff --git a/remix/app/components/Disclaimer.tsx b/remix/app/components/Disclaimer.tsx index d611d8a..622a04f 100644 --- a/remix/app/components/Disclaimer.tsx +++ b/remix/app/components/Disclaimer.tsx @@ -8,5 +8,5 @@ export default function Disclaimer() { save the entry at least once with a title before choosing an image.

- ); + ) } diff --git a/remix/app/components/FullSizeImage.tsx b/remix/app/components/FullSizeImage.tsx index c3d1c56..6b3e8b3 100644 --- a/remix/app/components/FullSizeImage.tsx +++ b/remix/app/components/FullSizeImage.tsx @@ -1,4 +1,4 @@ -import { ModalBody, ModalHeader } from "@contentstack/venus-components"; +import { ModalBody, ModalHeader } from "@contentstack/venus-components" export default function FullSizeImage({ title, @@ -7,11 +7,11 @@ export default function FullSizeImage({ alt, aspectRatio, }: { - title: string; - onClose: () => void; - imageUrl: string; - alt: string; - aspectRatio: number; + title: string + onClose: () => void + imageUrl: string + alt: string + aspectRatio: number }) { return ( <> @@ -37,5 +37,5 @@ export default function FullSizeImage({ /> - ); + ) } diff --git a/remix/app/components/ImageEditModal.tsx b/remix/app/components/ImageEditModal.tsx index 18addea..bc89912 100644 --- a/remix/app/components/ImageEditModal.tsx +++ b/remix/app/components/ImageEditModal.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, ChangeEvent } from "react"; +import { useState, useEffect, ChangeEvent } from "react" import { ModalFooter, ModalBody, @@ -8,59 +8,59 @@ import { Field as FieldComponent, FieldLabel, TextInput, -} from "@contentstack/venus-components"; +} from "@contentstack/venus-components" -import type { InsertResponse } from "~/types/imagevault"; +import type { InsertResponse } from "~/types/imagevault" type ImageEditModalProps = { - fieldData: InsertResponse; - setData: (data: InsertResponse) => void; - closeModal: () => void; -}; + fieldData: InsertResponse + setData: (data: InsertResponse) => void + closeModal: () => void +} export default function ImageEditModal({ fieldData, closeModal, setData, }: ImageEditModalProps) { - const [altText, setAltText] = useState(""); - const [caption, setCaption] = useState(""); + const [altText, setAltText] = useState("") + const [caption, setCaption] = useState("") - const assetUrl = fieldData.MediaConversions[0].Url; + const assetUrl = fieldData.MediaConversions[0].Url useEffect(() => { if (fieldData.Metadata && fieldData.Metadata.length) { const altText = fieldData.Metadata.find((meta) => meta.Name.includes("AltText_") - )?.Value; + )?.Value const caption = fieldData.Metadata.find((meta) => meta.Name.includes("Title_") - )?.Value; + )?.Value - setAltText(altText ?? ""); - setCaption(caption ?? ""); + setAltText(altText ?? "") + setCaption(caption ?? "") } - }, [fieldData.Metadata]); + }, [fieldData.Metadata]) function handleSave() { - const metaData = fieldData.Metadata ?? []; + const metaData = fieldData.Metadata ?? [] const newMetadata = metaData.map((meta) => { if (meta.Name.includes("AltText_")) { - return { ...meta, Value: altText }; + return { ...meta, Value: altText } } if (meta.Name.includes("Title_")) { - return { ...meta, Value: caption }; + return { ...meta, Value: caption } } - return meta; - }); + return meta + }) setData({ ...fieldData, Metadata: newMetadata, - }); - closeModal(); + }) + closeModal() } return ( @@ -120,5 +120,5 @@ export default function ImageEditModal({ - ); + ) } diff --git a/remix/app/components/ImageVaultDAM.tsx b/remix/app/components/ImageVaultDAM.tsx index b1aede2..c7d90f3 100644 --- a/remix/app/components/ImageVaultDAM.tsx +++ b/remix/app/components/ImageVaultDAM.tsx @@ -1,34 +1,34 @@ -import { useCallback, useEffect, useState } from "react"; -import { flushSync } from "react-dom"; +import { useCallback, useEffect, useState } from "react" +import { flushSync } from "react-dom" import { AssetCardVertical, Button, FieldLabel, cbModal, -} from "@contentstack/venus-components"; -import ImageEditModal from "./ImageEditModal"; -import FullSizeImage from "./FullSizeImage"; -import { isInsertResponse, openImageVault } from "~/utils/imagevault"; +} from "@contentstack/venus-components" +import ImageEditModal from "./ImageEditModal" +import FullSizeImage from "./FullSizeImage" +import { isInsertResponse, openImageVault } from "~/utils/imagevault" -import type { CbModalProps } from "@contentstack/venus-components/build/components/Modal/Modal"; -import type UiLocation from "@contentstack/app-sdk/dist/src/uiLocation"; -import type { InsertResponse } from "~/types/imagevault"; +import type { CbModalProps } from "@contentstack/venus-components/build/components/Modal/Modal" +import type UiLocation from "@contentstack/app-sdk/dist/src/uiLocation" +import type { InsertResponse } from "~/types/imagevault" import type { EntryDataPublishDetails, ImageVaultDAMConfig, -} from "~/utils/imagevault"; -import type { Lang } from "~/types/lang"; +} from "~/utils/imagevault" +import type { Lang } from "~/types/lang" export type ImageVaultDAMProps = { - sdk: UiLocation; - config: ImageVaultDAMConfig; - initialData: InsertResponse | null; -}; + sdk: UiLocation + config: ImageVaultDAMConfig + initialData: InsertResponse | null +} type DAMButtonProps = { - onClick: () => void; -}; + onClick: () => void +} function DAMButton({ onClick }: DAMButtonProps) { return ( @@ -45,27 +45,27 @@ function DAMButton({ onClick }: DAMButtonProps) { Choose a file - ); + ) } type MediaProps = { - media: InsertResponse; - onDelete: () => void; - onEdit: () => void; -}; + media: InsertResponse + onDelete: () => void + onEdit: () => void +} function Media({ media, onDelete, onEdit }: MediaProps) { // MediaConversions is an array but will only contain one object const { Url, Height, FormatHeight, Width, FormatWidth, Name, AspectRatio } = - media.MediaConversions[0]; + media.MediaConversions[0] - const assetUrl = Url; - const title = media.Name; - const width = FormatWidth || Width; - const height = FormatHeight || Height; + const assetUrl = Url + const title = media.Name + const width = FormatWidth || Width + const height = FormatHeight || Height const alt = media.Metadata?.find((meta) => meta.Name.includes("AltText_"))?.Value || - Name; + Name return (
@@ -91,7 +91,7 @@ function Media({ media, onDelete, onEdit }: MediaProps) { title={Name} aspectRatio={AspectRatio} /> - ); + ) }, modalProps: { size: "max", @@ -106,12 +106,12 @@ function Media({ media, onDelete, onEdit }: MediaProps) { overlay: {}, }, }, - }; - cbModal(cbModalProps); + } + cbModal(cbModalProps) }} />
- ); + ) } export default function ImageVaultDAM({ @@ -119,42 +119,42 @@ export default function ImageVaultDAM({ config, initialData, }: ImageVaultDAMProps) { - const [media, setMedia] = useState(initialData); + const [media, setMedia] = useState(initialData) - const field = sdk.location.CustomField?.field; - const frame = sdk.location.CustomField?.frame; - const entry = sdk.location.CustomField?.entry; - const stack = sdk.location.CustomField?.stack; + const field = sdk.location.CustomField?.field + const frame = sdk.location.CustomField?.frame + const entry = sdk.location.CustomField?.entry + const stack = sdk.location.CustomField?.stack const updateFrameHeight = useCallback(() => { if (frame?.updateHeight) { // We need to recalculate the height of the iframe when an image is added. // Cannot use flushSync inside useEffect. - setTimeout(() => frame.updateHeight(document.body.scrollHeight), 0); + setTimeout(() => frame.updateHeight(document.body.scrollHeight), 0) } - }, [frame]); + }, [frame]) const handleMedia = useCallback( (result?: InsertResponse) => { if (field && result) { flushSync(() => { - setMedia(result); - field.setData(result); - document.body.style.overflow = "hidden"; - }); + setMedia(result) + field.setData(result) + document.body.style.overflow = "hidden" + }) } - updateFrameHeight(); + updateFrameHeight() }, [field, updateFrameHeight] - ); + ) useEffect(() => { - updateFrameHeight(); - }, [updateFrameHeight]); + updateFrameHeight() + }, [updateFrameHeight]) const handleEdit = useCallback(() => { - const fieldData = field?.getData() as InsertResponse; + const fieldData = field?.getData() as InsertResponse if (isInsertResponse(fieldData)) { cbModal({ // @ts-expect-error: Component is badly typed @@ -168,12 +168,12 @@ export default function ImageVaultDAM({ modalProps: { size: "max", }, - }); + }) } - }, [field, handleMedia]); + }, [field, handleMedia]) if (!field || !frame || !entry || !stack) { - return

Initializing custom field...

; + return

Initializing custom field...

} const entryData: EntryDataPublishDetails = { @@ -186,7 +186,7 @@ export default function ImageVaultDAM({ entry.getField("title").getData().toString() || `Untitled (${entry.content_type.uid})`, uid: entry._data.uid, - }; + } return (
@@ -200,8 +200,8 @@ export default function ImageVaultDAM({ { - setMedia(null); - handleMedia(); + setMedia(null) + handleMedia() }} onEdit={handleEdit} /> @@ -212,10 +212,10 @@ export default function ImageVaultDAM({ config, entryData, onSuccess: handleMedia, - }); + }) }} /> )}
- ); + ) } diff --git a/remix/app/components/InvalidConfig.tsx b/remix/app/components/InvalidConfig.tsx index 761ca5e..9eb05f7 100644 --- a/remix/app/components/InvalidConfig.tsx +++ b/remix/app/components/InvalidConfig.tsx @@ -7,5 +7,5 @@ export default function InvalidConfig() { to run correctly.

- ); + ) } diff --git a/remix/app/entry.client.tsx b/remix/app/entry.client.tsx index 999c0a1..5d487b2 100644 --- a/remix/app/entry.client.tsx +++ b/remix/app/entry.client.tsx @@ -1,6 +1,6 @@ -import { RemixBrowser } from "@remix-run/react"; -import { startTransition, StrictMode } from "react"; -import { hydrateRoot } from "react-dom/client"; +import { RemixBrowser } from "@remix-run/react" +import { startTransition, StrictMode } from "react" +import { hydrateRoot } from "react-dom/client" startTransition(() => { hydrateRoot( @@ -8,5 +8,5 @@ startTransition(() => { - ); -}); + ) +}) diff --git a/remix/app/entry.server.tsx b/remix/app/entry.server.tsx index fc66fc8..662ff32 100644 --- a/remix/app/entry.server.tsx +++ b/remix/app/entry.server.tsx @@ -1,6 +1,6 @@ -import type { EntryContext } from "@remix-run/node"; -import { RemixServer } from "@remix-run/react"; -import { renderToString } from "react-dom/server"; +import type { EntryContext } from "@remix-run/node" +import { RemixServer } from "@remix-run/react" +import { renderToString } from "react-dom/server" export default function handleRequest( request: Request, @@ -10,10 +10,10 @@ export default function handleRequest( ) { let html = renderToString( - ); - html = "\n" + html; + ) + html = "\n" + html return new Response(html, { headers: { "Content-Type": "text/html" }, status: responseStatusCode, - }); + }) } diff --git a/remix/app/hooks/useApp.ts b/remix/app/hooks/useApp.ts index 9601299..88f2e34 100644 --- a/remix/app/hooks/useApp.ts +++ b/remix/app/hooks/useApp.ts @@ -1,46 +1,46 @@ -import UiLocation from "@contentstack/app-sdk/dist/src/uiLocation"; -import { useEffect, useState } from "react"; +import UiLocation from "@contentstack/app-sdk/dist/src/uiLocation" +import { useEffect, useState } from "react" export class SDKLoadingError extends Error {} export default function useApp() { - const [, setError] = useState(); - const [sdk, setSdk] = useState(); - const [config, setConfig] = useState>(); + const [, setError] = useState() + const [sdk, setSdk] = useState() + const [config, setConfig] = useState>() useEffect(() => { async function init() { try { const ContentstackAppSDK = (await import("@contentstack/app-sdk")) - .default; - const initSdk = await ContentstackAppSDK.init(); - setSdk(initSdk); + .default + const initSdk = await ContentstackAppSDK.init() + setSdk(initSdk) - const config = await initSdk.getConfig(); - setConfig(config); + const config = await initSdk.getConfig() + setConfig(config) - const iframeWrapperRef = document.getElementById("field"); - window.iframeRef = iframeWrapperRef; - window.postRobot = initSdk.postRobot; + const iframeWrapperRef = document.getElementById("field") + window.iframeRef = iframeWrapperRef + window.postRobot = initSdk.postRobot } catch (e) { - let err: Error; + let err: Error if (e instanceof Error) { - err = new SDKLoadingError(e.message); + err = new SDKLoadingError(e.message) } // Error boundaries do not support async functions. Workaround: // https://github.com/vercel/next.js/discussions/50564#discussioncomment-6063866 setError(() => { - throw err; - }); + throw err + }) } } - init(); - }, [setSdk, setConfig]); + init() + }, [setSdk, setConfig]) return { sdk, config, - }; + } } diff --git a/remix/app/root.tsx b/remix/app/root.tsx index 66e5076..13726dd 100644 --- a/remix/app/root.tsx +++ b/remix/app/root.tsx @@ -5,8 +5,8 @@ import { Scripts, ScrollRestoration, useRouteError, -} from '@remix-run/react'; -import { SDKLoadingError } from './hooks/useApp'; +} from "@remix-run/react" +import { SDKLoadingError } from "./hooks/useApp" export function Layout({ children }: { children: React.ReactNode }) { return ( @@ -31,20 +31,20 @@ export function Layout({ children }: { children: React.ReactNode }) { - ); + ) } export default function App() { - return ; + return } export function HydrateFallback() { - return

Loading...

; + return

Loading...

} export function ErrorBoundary() { - const error = useRouteError(); - console.error(error); + const error = useRouteError() + console.error(error) return ( @@ -68,5 +68,5 @@ export function ErrorBoundary() { - ); + ) } diff --git a/remix/app/routes/_index.tsx b/remix/app/routes/_index.tsx index 393057a..1dacfbb 100644 --- a/remix/app/routes/_index.tsx +++ b/remix/app/routes/_index.tsx @@ -1,15 +1,15 @@ -import type { MetaFunction } from '@remix-run/node'; +import type { MetaFunction } from "@remix-run/node" export const meta: MetaFunction = () => { return [ - { title: 'DAM: ImageVault integration' }, + { title: "DAM: ImageVault integration" }, { - name: 'description', - content: 'Integration of ImageVault as a DAM for Contentstack', + name: "description", + content: "Integration of ImageVault as a DAM for Contentstack", }, - ]; -}; + ] +} export default function Index() { - return null; + return null } diff --git a/remix/app/routes/config.tsx b/remix/app/routes/config.tsx index 0e032ad..637cf28 100644 --- a/remix/app/routes/config.tsx +++ b/remix/app/routes/config.tsx @@ -1,14 +1,14 @@ -import { Suspense, lazy } from "react"; -import useApp from "~/hooks/useApp"; +import { Suspense, lazy } from "react" +import useApp from "~/hooks/useApp" -const ConfigForm = lazy(() => import("~/components/ConfigForm")); +const ConfigForm = lazy(() => import("~/components/ConfigForm")) export default function ConfigPage() { - const { sdk, config } = useApp(); + const { sdk, config } = useApp() - const appConfigWidget = sdk?.location.AppConfigWidget; + const appConfigWidget = sdk?.location.AppConfigWidget - const setInstallationData = appConfigWidget?.installation.setInstallationData; + const setInstallationData = appConfigWidget?.installation.setInstallationData if (!config) { return ( @@ -17,7 +17,7 @@ export default function ConfigPage() { Could not fetch the current configuration, please try again later!

- ); + ) } if (!setInstallationData) { @@ -27,12 +27,12 @@ export default function ConfigPage() { THe configuration cannot be updated right now, please try again later!

- ); + ) } return ( Loading config form...

}>
- ); + ) } diff --git a/remix/app/routes/field.tsx b/remix/app/routes/field.tsx index fc72171..4e749da 100644 --- a/remix/app/routes/field.tsx +++ b/remix/app/routes/field.tsx @@ -1,26 +1,26 @@ -import { Suspense, lazy, useEffect, useState } from "react"; -import { useScript } from "usehooks-ts"; -import useApp from "~/hooks/useApp"; +import { Suspense, lazy, useEffect, useState } from "react" +import { useScript } from "usehooks-ts" +import useApp from "~/hooks/useApp" -import Disclaimer from "~/components/Disclaimer"; -import InvalidConfig from "~/components/InvalidConfig"; +import Disclaimer from "~/components/Disclaimer" +import InvalidConfig from "~/components/InvalidConfig" -import { isImageVaultDAMConfig } from "~/utils/imagevault"; -import type { InsertResponse } from "~/types/imagevault"; +import { isImageVaultDAMConfig } from "~/utils/imagevault" +import type { InsertResponse } from "~/types/imagevault" -const ImageVaultDAM = lazy(() => import("~/components/ImageVaultDAM")); +const ImageVaultDAM = lazy(() => import("~/components/ImageVaultDAM")) export default function Field() { - const { sdk, config } = useApp(); + const { sdk, config } = useApp() const ivStatus = useScript( "/scripts/imagevault-insert-media/insertmediawindow.min.js" - ); + ) - const [showDisclaimer, setShowDisclaimer] = useState(false); - const [fieldData, setFieldData] = useState(); + const [showDisclaimer, setShowDisclaimer] = useState(false) + const [fieldData, setFieldData] = useState() - const entry = sdk?.location.CustomField?.entry; - const field = sdk?.location.CustomField?.field; + const entry = sdk?.location.CustomField?.entry + const field = sdk?.location.CustomField?.field useEffect(() => { // If we can get field data from the SDK that means the entry has been @@ -29,30 +29,30 @@ export default function Field() { // cannot be referred to by ImageVault. Entry title is also required by us. try { if (field && entry) { - const data = field.getData(); - const title = entry.getField("title").getData().toString(); + const data = field.getData() + const title = entry.getField("title").getData().toString() if (data && title) { - setFieldData(data as InsertResponse); + setFieldData(data as InsertResponse) } else { - throw new Error("Missing data or title for entry"); + throw new Error("Missing data or title for entry") } } } catch (e) { - setShowDisclaimer(true); + setShowDisclaimer(true) } - }, [entry, field]); + }, [entry, field]) if (showDisclaimer) { - return ; + return } - const loaded = !!(fieldData && ivStatus === "ready" && sdk && config); + const loaded = !!(fieldData && ivStatus === "ready" && sdk && config) const initialData = - fieldData && Object.keys(fieldData).length > 0 ? fieldData : null; + fieldData && Object.keys(fieldData).length > 0 ? fieldData : null if (!loaded) { - return

Loading dependecies...

; + return

Loading dependecies...

} return ( @@ -65,5 +65,5 @@ export default function Field() { )} - ); + ) } diff --git a/remix/prettier.config.cjs b/remix/prettier.config.cjs new file mode 100644 index 0000000..2fd3b38 --- /dev/null +++ b/remix/prettier.config.cjs @@ -0,0 +1,8 @@ +module.exports = { + semi: false, + trailingComma: "es5", + singleQuote: false, + printWidth: 80, + tabWidth: 2, + endOfLine: "lf", +} diff --git a/remix/vite.config.ts b/remix/vite.config.ts index 3b7f37b..61ddcc3 100644 --- a/remix/vite.config.ts +++ b/remix/vite.config.ts @@ -1,6 +1,6 @@ -import { vitePlugin as remix } from '@remix-run/dev'; -import { defineConfig } from 'vite'; -import tsconfigPaths from 'vite-tsconfig-paths'; +import { vitePlugin as remix } from "@remix-run/dev" +import { defineConfig } from "vite" +import tsconfigPaths from "vite-tsconfig-paths" export default defineConfig({ plugins: [ @@ -12,4 +12,4 @@ export default defineConfig({ build: { emptyOutDir: true, }, -}); +}) diff --git a/rte/.prettierignore b/rte/.prettierignore new file mode 100644 index 0000000..8b9c64f --- /dev/null +++ b/rte/.prettierignore @@ -0,0 +1,7 @@ +# Files +.eslintrc.cjs +.prettierignore +package.json +package-lock.json +prettier.config.cjs +tsconfig.json diff --git a/rte/components/EmbedBtn.tsx b/rte/components/EmbedBtn.tsx index df0a2ca..6a68e48 100644 --- a/rte/components/EmbedBtn.tsx +++ b/rte/components/EmbedBtn.tsx @@ -1,11 +1,11 @@ -import React, { PropsWithChildren } from 'react'; -import { Tooltip } from '@contentstack/venus-components'; +import React, { PropsWithChildren } from "react" +import { Tooltip } from "@contentstack/venus-components" type EmbedBtnProps = PropsWithChildren & { - content: string; - onClick: (e: unknown) => void; - title: string; -}; + content: string + onClick: (e: unknown) => void + title: string +} export default function EmbedBtn({ content, @@ -19,13 +19,13 @@ export default function EmbedBtn({ id={title} type="button" onClick={(e) => { - e?.preventDefault(); - e?.stopPropagation(); - onClick(e); + e?.preventDefault() + e?.stopPropagation() + onClick(e) }} > {children} - ); + ) } diff --git a/rte/components/ImageEditModal.tsx b/rte/components/ImageEditModal.tsx index 88decf9..bdfd8a1 100644 --- a/rte/components/ImageEditModal.tsx +++ b/rte/components/ImageEditModal.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, ChangeEvent } from 'react'; +import React, { useState, useEffect, ChangeEvent } from "react" import { ModalFooter, ModalBody, @@ -9,14 +9,14 @@ import { FieldLabel, TextInput, Select, -} from '@contentstack/venus-components'; -import { Path } from 'slate'; +} from "@contentstack/venus-components" +import { Path } from "slate" import type { IRteParam, IRteElementType, -} from '@contentstack/app-sdk/dist/src/RTE/types'; -import type { InsertResponse } from "~/types/imagevault"; +} from "@contentstack/app-sdk/dist/src/RTE/types" +import type { InsertResponse } from "~/types/imagevault" enum DropdownValues { center = "center", @@ -26,10 +26,10 @@ enum DropdownValues { } type DropDownItem = { - label: string; - value: DropdownValues; - type: string; -}; + label: string + value: DropdownValues + type: string +} const dropdownList: DropDownItem[] = [ { @@ -52,16 +52,16 @@ const dropdownList: DropDownItem[] = [ value: DropdownValues.right, type: "select", }, -]; +] type ImageEditModalProps = { element: IRteElementType & { - attrs: InsertResponse; - }; - rte: IRteParam; - closeModal: () => void; - path: Path; -}; + attrs: InsertResponse + } + rte: IRteParam + closeModal: () => void + path: Path +} export default function ImageEditModal({ element, @@ -73,29 +73,29 @@ export default function ImageEditModal({ label: "None", value: DropdownValues.none, type: "select", - }); - const [altText, setAltText] = useState(""); - const [caption, setCaption] = useState(""); + }) + const [altText, setAltText] = useState("") + const [caption, setCaption] = useState("") - const assetUrl = element.attrs.MediaConversions[0].Url; + const assetUrl = element.attrs.MediaConversions[0].Url useEffect(() => { if (element.attrs.Metadata && element.attrs.Metadata.length) { const altText = element.attrs.Metadata.find((meta) => meta.Name.includes("AltText_") - )?.Value; + )?.Value const caption = element.attrs.Metadata.find((meta) => meta.Name.includes("Title_") - )?.Value; + )?.Value - setAltText(altText ?? ""); - setCaption(caption ?? ""); + setAltText(altText ?? "") + setCaption(caption ?? "") } - }, [element.attrs.Metadata]); + }, [element.attrs.Metadata]) function handleSave() { - let newStyle; + let newStyle switch (alignment.value) { case DropdownValues.center: @@ -106,25 +106,25 @@ export default function ImageEditModal({ maxWidth: element.attrs.width ? `${element.attrs.width}px` : undefined, - }; - break; + } + break case DropdownValues.none: default: - newStyle = {}; - break; + newStyle = {} + break } - const metaData = element.attrs.Metadata ?? []; + const metaData = element.attrs.Metadata ?? [] const newMetadata = metaData.map((meta) => { if (meta.Name.includes("AltText_")) { - return { ...meta, Value: altText }; + return { ...meta, Value: altText } } if (meta.Name.includes("Title_")) { - return { ...meta, Value: caption }; + return { ...meta, Value: caption } } - return meta; - }); + return meta + }) rte._adv.Transforms?.setNodes( rte._adv.editor, @@ -137,9 +137,9 @@ export default function ImageEditModal({ }, }, { at: path } - ); + ) - closeModal(); + closeModal() } return ( @@ -168,7 +168,7 @@ export default function ImageEditModal({ selectLabel="Alignment" value={alignment} onChange={(e: DropDownItem) => { - setAlignment(e); + setAlignment(e) }} options={dropdownList} /> @@ -209,5 +209,5 @@ export default function ImageEditModal({ - ); + ) } diff --git a/rte/components/ImageElement.tsx b/rte/components/ImageElement.tsx index 07126b9..c65629b 100644 --- a/rte/components/ImageElement.tsx +++ b/rte/components/ImageElement.tsx @@ -1,27 +1,27 @@ -import React, { useRef, useCallback, PropsWithChildren } from 'react'; -import { Tooltip, Icon, cbModal } from '@contentstack/venus-components'; +import React, { useRef, useCallback, PropsWithChildren } from "react" +import { Tooltip, Icon, cbModal } from "@contentstack/venus-components" -import { Resizable } from 're-resizable'; -import EmbedBtn from './EmbedBtn'; -import ImageEditModal from './ImageEditModal'; +import { Resizable } from "re-resizable" +import EmbedBtn from "./EmbedBtn" +import ImageEditModal from "./ImageEditModal" import type { IRteParam, IRteElementType, -} from '@contentstack/app-sdk/dist/src/RTE/types'; -import type { InsertResponse } from '~/types/imagevault'; +} from "@contentstack/app-sdk/dist/src/RTE/types" +import type { InsertResponse } from "~/types/imagevault" type ImageElementProps = PropsWithChildren & { - element: IRteElementType & { attrs: InsertResponse }; - rte: IRteParam; -}; + element: IRteElementType & { attrs: InsertResponse } + rte: IRteParam +} export function ImageElement({ children, element, rte }: ImageElementProps) { - const assetUrl = element.attrs.MediaConversions[0].Url; - const isSelected = rte?.selection?.isSelected(); - const isFocused = rte?.selection?.isFocused(); - const isHighlight = isFocused && isSelected; + const assetUrl = element.attrs.MediaConversions[0].Url + const isSelected = rte?.selection?.isSelected() + const isFocused = rte?.selection?.isFocused() + const isHighlight = isFocused && isSelected - const imgRef = useRef(null); + const imgRef = useRef(null) const handleEdit = useCallback(() => { cbModal({ @@ -36,8 +36,8 @@ export function ImageElement({ children, element, rte }: ImageElementProps) { modalProps: { size: "max", }, - }); - }, [element, rte]); + }) + }, [element, rte]) const ToolTipButtons = () => { return ( @@ -54,12 +54,12 @@ export function ImageElement({ children, element, rte }: ImageElementProps) { - ); - }; + ) + } const onResizeStop = () => { - const { attrs: elementAttrs } = element; - const { offsetWidth: width, offsetHeight: height } = imgRef?.current ?? {}; + const { attrs: elementAttrs } = element + const { offsetWidth: width, offsetHeight: height } = imgRef?.current ?? {} const newAttrs: { [key: string]: unknown } = { ...elementAttrs, @@ -70,24 +70,24 @@ export function ImageElement({ children, element, rte }: ImageElementProps) { ...(width && height ? { width: `${width.toString()}px`, height: `${height.toString()}px` } : {}), - }; + } rte?._adv?.Transforms?.setNodes( rte._adv.editor, { attrs: newAttrs }, { at: rte.getPath(element) } - ); - }; + ) + } - let alignmentStyles = {}; + let alignmentStyles = {} const marginAlignment: Record = { center: { margin: "auto" }, left: { marginRight: "auto" }, right: { marginLeft: "auto" }, - }; + } if (typeof element.attrs.position === "string") { - alignmentStyles = marginAlignment[element.attrs.position]; + alignmentStyles = marginAlignment[element.attrs.position] } return ( @@ -137,7 +137,7 @@ export function ImageElement({ children, element, rte }: ImageElementProps) { { - event.currentTarget.src = "https://placehold.co/600x400"; + event.currentTarget.src = "https://placehold.co/600x400" }} style={{ width: "100%", @@ -169,5 +169,5 @@ export function ImageElement({ children, element, rte }: ImageElementProps) { - ); + ) } diff --git a/rte/env.d.ts b/rte/env.d.ts index bf5a50b..1f16f11 100644 --- a/rte/env.d.ts +++ b/rte/env.d.ts @@ -1 +1 @@ -declare const IS_DEV: boolean; +declare const IS_DEV: boolean diff --git a/rte/main.tsx b/rte/main.tsx index 6fac35a..6992bec 100644 --- a/rte/main.tsx +++ b/rte/main.tsx @@ -1,70 +1,70 @@ -import React from 'react'; -import ContentstackSDK from '@contentstack/app-sdk'; +import React from "react" +import ContentstackSDK from "@contentstack/app-sdk" -import { ImageElement } from '~/components/ImageElement'; +import { ImageElement } from "~/components/ImageElement" -import { Icon } from '@contentstack/venus-components'; -import { openImageVault } from '~/utils/imagevault'; +import { Icon } from "@contentstack/venus-components" +import { openImageVault } from "~/utils/imagevault" -import type { Lang } from '~/types/lang'; +import type { Lang } from "~/types/lang" import type { ContentstackEmbeddedData, ContentstackPluginDefinition, ExtractedContentstackEmbeddedData, -} from '~/types/contentstack'; +} from "~/types/contentstack" function findThisPlugin(ext: ContentstackPluginDefinition) { - return ext.type === 'rte_plugin' && ext.title === 'ImageVault'; + return ext.type === "rte_plugin" && ext.title === "ImageVault" } async function loadScript(url: string) { return new Promise((resolve, reject) => { if (!document) { - throw new Error('Run in browser only'); + throw new Error("Run in browser only") } - const head = document.head || document.getElementsByTagName('head')[0]; + const head = document.head || document.getElementsByTagName("head")[0] - const script = document.createElement('script'); - script.type = 'text/javascript'; - script.async = true; - script.src = url; + const script = document.createElement("script") + script.type = "text/javascript" + script.async = true + script.src = url script.onload = function () { - this.onerror = this.onload = null; - resolve(window.ImageVault); - }; + this.onerror = this.onload = null + resolve(window.ImageVault) + } script.onerror = function () { - this.onerror = this.onload = null; - reject(`Failed to load ${this.src}`); - }; + this.onerror = this.onload = null + reject(`Failed to load ${this.src}`) + } - head.appendChild(script); - }); + head.appendChild(script) + }) } function extractContentstackEmbeddedData( jwtLike: string ): ExtractedContentstackEmbeddedData | null { try { - const base64str = jwtLike.replace(/-/g, "+").replace(/_/g, "/"); + const base64str = jwtLike.replace(/-/g, "+").replace(/_/g, "/") const jsonStr = decodeURIComponent( window .atob(base64str) .split("") .map(function (c) { - return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2); + return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2) }) .join("") - ); + ) - const json = JSON.parse(jsonStr); - json.exports = JSON.parse(json.exports); - json.props.value = JSON.parse(json.props.value); + const json = JSON.parse(jsonStr) + json.exports = JSON.parse(json.exports) + json.props.value = JSON.parse(json.props.value) if (IS_DEV) { - console.log(`Contentstack Embedded Data`, json); + console.log(`Contentstack Embedded Data`, json) } const { @@ -77,12 +77,12 @@ function extractContentstackEmbeddedData( stack: { api_key }, branch, }, - }: ContentstackEmbeddedData = json.props.value; + }: ContentstackEmbeddedData = json.props.value - const titleField = schema.find((f) => f.uid === "title"); + const titleField = schema.find((f) => f.uid === "title") // We force this value with ! because we know it exists. // Otherwise this code would not run. - const plugin = extensions.find(findThisPlugin)!; + const plugin = extensions.find(findThisPlugin)! return { stack: { @@ -101,43 +101,43 @@ function extractContentstackEmbeddedData( uid: entryMetadata.entryUid, }, plugin, - }; + } } catch (e) { - console.log(`Unable to parse JWT like: ${jwtLike}`); + console.log(`Unable to parse JWT like: ${jwtLike}`) } - return null; + return null } -let ivloaded = false; +let ivloaded = false function loadIV(plugin: ContentstackPluginDefinition) { if (plugin.src) { - const url = new URL(plugin.src); - url.pathname = "/scripts/imagevault-insert-media/insertmediawindow.min.js"; + const url = new URL(plugin.src) + url.pathname = "/scripts/imagevault-insert-media/insertmediawindow.min.js" if (IS_DEV) { - console.log(`Loading script: ${url.toString()}`); + console.log(`Loading script: ${url.toString()}`) } - loadScript(url.toString()); - ivloaded = true; + loadScript(url.toString()) + ivloaded = true } } async function init() { try { - const sdk = await ContentstackSDK.init(); + const sdk = await ContentstackSDK.init() - const extensionObj = sdk?.location; - const RTE = extensionObj?.RTEPlugin; + const extensionObj = sdk?.location + const RTE = extensionObj?.RTEPlugin - let embeddedData: ExtractedContentstackEmbeddedData | null = null; - const jwtLike = window.name.split("__").find((str) => str.startsWith("ey")); + let embeddedData: ExtractedContentstackEmbeddedData | null = null + const jwtLike = window.name.split("__").find((str) => str.startsWith("ey")) if (jwtLike) { - embeddedData = extractContentstackEmbeddedData(jwtLike); + embeddedData = extractContentstackEmbeddedData(jwtLike) if (embeddedData?.plugin) { - loadIV(embeddedData.plugin); + loadIV(embeddedData.plugin) } } - if (!RTE) return; + if (!RTE) return const ImageVault = RTE("ImageVault", (rte) => { if (rte) { @@ -145,11 +145,11 @@ async function init() { // Loading failed via embedded data above, try again with data inside RTE // @ts-expect-error: incorrect typings, requestProps is available at runtime const extensions = rte._adv.editor.requestProps - .extensions as ContentstackPluginDefinition[]; - const plugin = extensions.find(findThisPlugin); + .extensions as ContentstackPluginDefinition[] + const plugin = extensions.find(findThisPlugin) if (plugin) { - loadIV(plugin); + loadIV(plugin) } } } @@ -163,35 +163,35 @@ async function init() { {props.children} - ); + ) } else { - console.error("No instance of RTE found"); + console.error("No instance of RTE found") return (

An unexpected error occured, see console for more info.

- ); + ) } }, display: ["toolbar"], elementType: ["void"], - }; - }); + } + }) ImageVault.on("exec", async (rte) => { if (rte) { - const savedSelection = rte?.selection?.get() ?? undefined; + const savedSelection = rte?.selection?.get() ?? undefined // @ts-expect-error: Added at runtime - const appConfig = await rte.getConfig(); + const appConfig = await rte.getConfig() // This is the configuration for this instance of the plugin. // You edit this in the content types settings RTE plugin section. // @ts-expect-error: Added at runtime - const pluginConfig = await rte.getFieldConfig(); + const pluginConfig = await rte.getFieldConfig() const config = { ...appConfig, ...pluginConfig, - }; + } openImageVault({ config, @@ -218,17 +218,17 @@ async function init() { children: [{ text: "" }], }, { at: savedSelection } - ); + ) }, - }); + }) } - }); + }) return { ImageVault, - }; + } } catch (e) { - console.error({ e }); + console.error({ e }) } } -export default init(); +export default init() diff --git a/rte/prettier.config.cjs b/rte/prettier.config.cjs new file mode 100644 index 0000000..2fd3b38 --- /dev/null +++ b/rte/prettier.config.cjs @@ -0,0 +1,8 @@ +module.exports = { + semi: false, + trailingComma: "es5", + singleQuote: false, + printWidth: 80, + tabWidth: 2, + endOfLine: "lf", +} diff --git a/rte/vite.config.ts b/rte/vite.config.ts index 7bc7e24..8bba6f5 100644 --- a/rte/vite.config.ts +++ b/rte/vite.config.ts @@ -1,28 +1,28 @@ -import { resolve } from 'path'; -import { defineConfig } from 'vite'; -import tsconfigPaths from 'vite-tsconfig-paths'; +import { resolve } from "path" +import { defineConfig } from "vite" +import tsconfigPaths from "vite-tsconfig-paths" export default defineConfig({ plugins: [tsconfigPaths()], define: { - IS_DEV: process.env.IS_DEV === 'true' ? true : false, + IS_DEV: process.env.IS_DEV === "true" ? true : false, }, publicDir: false, build: { - sourcemap: process.env.IS_DEV ? 'inline' : 'hidden', + sourcemap: process.env.IS_DEV ? "inline" : "hidden", emptyOutDir: true, lib: { - entry: resolve(__dirname, 'main.tsx'), - name: 'csiv', - fileName: () => (process.env.IS_DEV ? 'csiv.js' : 'csiv-[hash].js'), + entry: resolve(__dirname, "main.tsx"), + name: "csiv", + fileName: () => (process.env.IS_DEV ? "csiv.js" : "csiv-[hash].js"), // @ts-expect-error: 'system' not valid by typings, but works with Rollup - formats: ['system'], + formats: ["system"], }, rollupOptions: { - external: ['react', 'react-dom', '@contentstack/venus-components'], + external: ["react", "react-dom", "@contentstack/venus-components"], output: { - dir: '../remix/public/build/rte', + dir: "../remix/public/build/rte", }, }, }, -}); +})