diff --git a/packages/editor/src/ee/helpers/parser.ts b/packages/editor/src/ee/helpers/parser.ts new file mode 100644 index 0000000000..18b6e9f303 --- /dev/null +++ b/packages/editor/src/ee/helpers/parser.ts @@ -0,0 +1,46 @@ +/** + * @description function to extract all additional assets from HTML content + * @param htmlContent + * @returns {string[]} array of additional asset sources + */ +export const extractAdditionalAssetsFromHTMLContent = (htmlContent: string): string[] => { + // create a DOM parser + const parser = new DOMParser(); + // parse the HTML string into a DOM document + const doc = parser.parseFromString(htmlContent, "text/html"); + // collect all unique asset sources + const assetSources = new Set(); + // extract sources from attachment components + const attachmentComponents = doc.querySelectorAll("attachment-component"); + attachmentComponents.forEach((component) => { + const src = component.getAttribute("src"); + if (src) assetSources.add(src); + }); + return Array.from(assetSources); +}; + +/** + * @description function to replace additional assets in HTML content with new IDs + * @param props + * @returns {string} HTML content with replaced additional assets + */ +export const replaceAdditionalAssetsInHTMLContent = (props: { + htmlContent: string; + assetMap: Record; +}): string => { + const { htmlContent, assetMap } = props; + // create a DOM parser + const parser = new DOMParser(); + // parse the HTML string into a DOM document + const doc = parser.parseFromString(htmlContent, "text/html"); + // replace sources in attachment components + const attachmentComponents = doc.querySelectorAll("attachment-component"); + attachmentComponents.forEach((component) => { + const oldSrc = component.getAttribute("src"); + if (oldSrc && assetMap[oldSrc]) { + component.setAttribute("src", assetMap[oldSrc]); + } + }); + // serialize the document back into a string + return doc.body.innerHTML; +}; diff --git a/web/ee/components/epics/peek-overview/view.tsx b/web/ee/components/epics/peek-overview/view.tsx index 84d31ab187..c1e9aee6b6 100644 --- a/web/ee/components/epics/peek-overview/view.tsx +++ b/web/ee/components/epics/peek-overview/view.tsx @@ -1,17 +1,19 @@ import { FC, useRef, useState } from "react"; import { observer } from "mobx-react"; +// plane imports import { EIssueServiceType } from "@plane/constants"; +import { cn } from "@plane/utils"; +// components import { TIssueOperations } from "@/components/issues"; -// helpers -import { cn } from "@plane/utils"; // hooks import { useIssueDetail } from "@/hooks/store"; import useKeypress from "@/hooks/use-keypress"; import usePeekOverviewOutsideClickDetector from "@/hooks/use-peek-overview-outside-click"; -// local components +// plane web imports import { useCustomers } from "@/plane-web/hooks/store"; import { useEpics } from "@/plane-web/hooks/store/epics/use-epics"; import { useInitiatives } from "@/plane-web/hooks/store/use-initiatives"; +// local imports import { EpicDetailRoot } from "../details/root"; import { EpicPeekOverviewError } from "./error"; import { EpicPeekOverviewHeader, TPeekModes } from "./header"; @@ -95,8 +97,9 @@ export const EpicView: FC = observer((props) => { const handleKeyDown = () => { const slashCommandDropdownElement = document.querySelector("#slash-command"); + const editorImageFullScreenModalElement = document.querySelector(".editor-image-full-screen-modal"); const dropdownElement = document.activeElement?.tagName === "INPUT"; - if (!isAnyModalOpen && !slashCommandDropdownElement && !dropdownElement) { + if (!isAnyModalOpen && !slashCommandDropdownElement && !dropdownElement && !editorImageFullScreenModalElement) { removeRoutePeekId(); const issueElement = document.getElementById(`issue-${issueId}`); if (issueElement) issueElement?.focus(); diff --git a/web/ee/components/pages/modals/template-select-modal.tsx b/web/ee/components/pages/modals/template-select-modal.tsx index cbeaa4bddc..39c362ab3c 100644 --- a/web/ee/components/pages/modals/template-select-modal.tsx +++ b/web/ee/components/pages/modals/template-select-modal.tsx @@ -3,7 +3,7 @@ import { observer } from "mobx-react"; import { Check, Search, Shapes } from "lucide-react"; import { Combobox } from "@headlessui/react"; // plane imports -import { EditorTitleRefApi, getEditorContentWithReplacedImageAssets } from "@plane/editor"; +import { EditorTitleRefApi, getEditorContentWithReplacedAssets } from "@plane/editor"; import { useTranslation } from "@plane/i18n"; import { FileService } from "@plane/services"; import { TPageTemplate } from "@plane/types"; @@ -68,7 +68,7 @@ export const TemplateSelectModal: React.FC = observer setIsApplyingTemplate(true); // duplicate template assets and replace the old assets with the new ones const duplicateAssetService = fileService.duplicateAssets.bind(fileService, workspaceSlug); - const documentPayload = await getEditorContentWithReplacedImageAssets({ + const documentPayload = await getEditorContentWithReplacedAssets({ descriptionHTML: selectedTemplate.template_data.description_html ?? "", entityId: page.id, entityType: EFileAssetType.PAGE_DESCRIPTION, diff --git a/web/ee/components/templates/settings/list/action-wrapper.tsx b/web/ee/components/templates/settings/list/action-wrapper.tsx index ee529fc90b..197e052338 100644 --- a/web/ee/components/templates/settings/list/action-wrapper.tsx +++ b/web/ee/components/templates/settings/list/action-wrapper.tsx @@ -2,7 +2,7 @@ import React, { useState } from "react"; import { observer } from "mobx-react"; // plane imports import { EIssuesStoreType, ETemplateLevel, ETemplateType } from "@plane/constants"; -import { getEditorContentWithReplacedImageAssets } from "@plane/editor"; +import { getEditorContentWithReplacedAssets } from "@plane/editor"; import { useTranslation } from "@plane/i18n"; import { FileService } from "@plane/services"; import { TPage } from "@plane/types"; @@ -107,7 +107,7 @@ export const TemplateListActionWrapper = observer((props: TTemplateListActionWra // duplicate the assets const duplicateAssetService = fileService.duplicateAssets.bind(fileService, workspaceSlug); - const documentPayload = await getEditorContentWithReplacedImageAssets({ + const documentPayload = await getEditorContentWithReplacedAssets({ descriptionHTML: pageData.description_html ?? "", entityId: page.id, entityType: EFileAssetType.PAGE_DESCRIPTION, diff --git a/web/ee/components/templates/settings/publish/form/basic-details.tsx b/web/ee/components/templates/settings/publish/form/basic-details.tsx index 792cb291a2..06af633a12 100644 --- a/web/ee/components/templates/settings/publish/form/basic-details.tsx +++ b/web/ee/components/templates/settings/publish/form/basic-details.tsx @@ -135,13 +135,11 @@ export const TemplateBasicDetails = observer(

"} - workspaceSlug={workspaceSlug} + workspaceSlug={workspaceSlug.toString()} workspaceId={workspaceId} - projectId={projectId ? projectId : undefined} + projectId={projectId ?? undefined} ref={editorRef} - onChange={(description_json: object, description_html: string) => { - onChange(description_html); - }} + onChange={(_description_json, description_html) => onChange(description_html)} searchMentionCallback={searchEntity} placeholder={(isFocused, value) => isEditorEmpty(value) @@ -149,7 +147,7 @@ export const TemplateBasicDetails = observer( : t(`${getDescriptionPlaceholderI18n(isFocused, value)}`) } containerClassName="min-h-[240px] md:min-h-[120px] border-[0.5px] py-2 border-custom-border-200 rounded-md" - disabledExtensions={["image", "issue-embed"]} + disabledExtensions={["image", "issue-embed", "attachments"]} uploadFile={() => Promise.resolve("")} /> )} diff --git a/web/ee/components/templates/settings/work-item/form/work-item-details.tsx b/web/ee/components/templates/settings/work-item/form/work-item-details.tsx index 96c3eb6060..310407f76e 100644 --- a/web/ee/components/templates/settings/work-item/form/work-item-details.tsx +++ b/web/ee/components/templates/settings/work-item/form/work-item-details.tsx @@ -106,21 +106,19 @@ export const WorkItemDetails = observer((props: TWorkItemDetailsProps) => {

"} - workspaceSlug={workspaceSlug as string} + workspaceSlug={workspaceSlug.toString()} workspaceId={workspaceId} - projectId={projectId ? projectId : undefined} + projectId={projectId ?? undefined} ref={editorRef} searchMentionCallback={searchEntity} - onChange={(description_json: object, description_html: string) => { - onChange(description_html); - }} + onChange={(_description_json, description_html) => onChange(description_html)} placeholder={(isFocused, value) => isEditorEmpty(value) ? t("templates.settings.form.work_item.description.placeholder") : t(`${getDescriptionPlaceholderI18n(isFocused, value)}`) } containerClassName="min-h-[80px] px-0" - disabledExtensions={["image"]} + disabledExtensions={["image", "attachments"]} uploadFile={async (blockId, file) => { try { const { asset_id } = await uploadEditorAsset({