@chayns-components/core 5.5.27 → 5.5.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +875 -64
- package/lib/cjs/components/copyable-content/CopyableContent.js +4 -3
- package/lib/cjs/components/copyable-content/CopyableContent.js.map +1 -1
- package/lib/cjs/components/copyable-content/CopyableContent.test.js +14 -0
- package/lib/cjs/components/copyable-content/CopyableContent.test.js.map +1 -1
- package/lib/cjs/components/copyable-content/copyableContentClipboard.js +6 -5
- package/lib/cjs/components/copyable-content/copyableContentClipboard.js.map +1 -1
- package/lib/cjs/components/copyable-content/copyableContentClipboard.test.js +6 -0
- package/lib/cjs/components/copyable-content/copyableContentClipboard.test.js.map +1 -1
- package/lib/esm/components/copyable-content/CopyableContent.js +4 -3
- package/lib/esm/components/copyable-content/CopyableContent.js.map +1 -1
- package/lib/esm/components/copyable-content/CopyableContent.test.js +14 -0
- package/lib/esm/components/copyable-content/CopyableContent.test.js.map +1 -1
- package/lib/esm/components/copyable-content/copyableContentClipboard.js +6 -5
- package/lib/esm/components/copyable-content/copyableContentClipboard.js.map +1 -1
- package/lib/esm/components/copyable-content/copyableContentClipboard.test.js +6 -0
- package/lib/esm/components/copyable-content/copyableContentClipboard.test.js.map +1 -1
- package/lib/types/components/copyable-content/CopyableContent.d.ts +4 -0
- package/lib/types/components/copyable-content/copyableContentClipboard.d.ts +1 -1
- package/package.json +4 -4
|
@@ -31,7 +31,8 @@ const CopyableContent = ({
|
|
|
31
31
|
content,
|
|
32
32
|
children,
|
|
33
33
|
copyFailedMessage,
|
|
34
|
-
collapsedHeight
|
|
34
|
+
collapsedHeight,
|
|
35
|
+
transformClipboardHtml
|
|
35
36
|
}) => {
|
|
36
37
|
const rootRef = (0, _react.useRef)(null);
|
|
37
38
|
const actionGroupRef = (0, _react.useRef)(null);
|
|
@@ -62,7 +63,7 @@ const CopyableContent = ({
|
|
|
62
63
|
}, []);
|
|
63
64
|
const handleCopy = (0, _react.useCallback)(async () => {
|
|
64
65
|
try {
|
|
65
|
-
await (0, _copyableContentClipboard.copyableContentToClipboard)(content);
|
|
66
|
+
await (0, _copyableContentClipboard.copyableContentToClipboard)(content, transformClipboardHtml);
|
|
66
67
|
showCopyFeedback();
|
|
67
68
|
} catch {
|
|
68
69
|
void (0, _chaynsApi.createDialog)({
|
|
@@ -72,7 +73,7 @@ const CopyableContent = ({
|
|
|
72
73
|
type: _chaynsApi.DialogType.TOAST
|
|
73
74
|
}).open();
|
|
74
75
|
}
|
|
75
|
-
}, [content, defaultCopyFailedMessage, copyFailedMessage, showCopyFeedback]);
|
|
76
|
+
}, [content, defaultCopyFailedMessage, copyFailedMessage, showCopyFeedback, transformClipboardHtml]);
|
|
76
77
|
return /*#__PURE__*/_react.default.createElement(_CopyableContent.StyledCopyableContent, {
|
|
77
78
|
$appearance: appearance,
|
|
78
79
|
$colorMode: colorMode,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CopyableContent.js","names":["_format","require","_textstring","_chaynsApi","_react","_interopRequireWildcard","_textStrings","_interopRequireDefault","_useStickyActionState","_ColorSchemeProvider","_SharingContextMenu","_Icon","_Truncation","_CopyableContent","_CopyableContent2","_copyableContentClipboard","e","__esModule","default","t","WeakMap","r","n","o","i","f","__proto__","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","COPY_FEEDBACK_DURATION","CopyableContent","appearance","CopyableContentAppearance","Default","content","children","copyFailedMessage","collapsedHeight","rootRef","useRef","actionGroupRef","copyFeedbackTimeoutRef","isActionGroupSticky","useStickyActionState","hasCopied","setHasCopied","useState","colorScheme","useColorScheme","colorMode","theme","CopyableContentColorMode","Dark","Light","defaultCopyButtonText","useTextstringValue","textstring","ttsToITextString","textStrings","components","copyableContent","copy","defaultCopyFailedMessage","copyFailed","shareText","share","html","useMemo","formatStringToHtml","useEffect","window","clearTimeout","current","showCopyFeedback","useCallback","setTimeout","handleCopy","copyableContentToClipboard","createDialog","showCloseIcon","text","toastType","ToastType","ERROR","type","DialogType","TOAST","open","createElement","StyledCopyableContent","$appearance","$colorMode","className","ref","StyledCopyableContentActions","StyledCopyableContentActionGroup","StyledCopyableContentButton","$isSticky","onClick","icons","link","shouldShowCallingCodeAction","shouldShowCopyAction","shouldUseDefaultTriggerStyles","StyledCopyableContentTruncation","StyledCopyableContentBody","$shouldShowPadding","dangerouslySetInnerHTML","__html","displayName","_default","exports"],"sources":["../../../../src/components/copyable-content/CopyableContent.tsx"],"sourcesContent":["import { formatStringToHtml } from '@chayns-components/format';\nimport { ttsToITextString, useTextstringValue } from '@chayns-components/textstring';\nimport { createDialog, DialogType, ToastType } from 'chayns-api';\nimport React, { FC, ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport textStrings from '../../constants/textStrings';\nimport { useStickyActionState } from '../../hooks/useStickyActionState';\nimport { useColorScheme } from '../color-scheme-provider/ColorSchemeProvider';\nimport SharingContextMenu from '../sharing-context-menu/SharingContextMenu';\nimport Icon from '../icon/Icon';\nimport Truncation from '../truncation/Truncation';\nimport {\n CopyableContentColorMode,\n StyledCopyableContent,\n StyledCopyableContentActionGroup,\n StyledCopyableContentActions,\n StyledCopyableContentBody,\n StyledCopyableContentButton,\n StyledCopyableContentTruncation,\n} from './CopyableContent.styles';\nimport { CopyableContentAppearance } from './CopyableContent.types';\nimport { copyableContentToClipboard } from './copyableContentClipboard';\n\nconst COPY_FEEDBACK_DURATION = 1500;\n\nexport type CopyableContentProps = {\n /**\n * Controls the visual surface of the content block.\n */\n appearance?: CopyableContentAppearance;\n /**\n * Markdown source used for rendering and clipboard data.\n */\n content: string;\n /**\n * Replaces only the visible rendered content and never the copied source.\n */\n children?: ReactNode;\n /**\n * Replaces the localized error message shown when copying fails.\n */\n copyFailedMessage?: string;\n /**\n * The height of the content in its collapsed state.\n */\n collapsedHeight?: number;\n};\n\nconst CopyableContent: FC<CopyableContentProps> = ({\n appearance = CopyableContentAppearance.Default,\n content,\n children,\n copyFailedMessage,\n collapsedHeight,\n}) => {\n const rootRef = useRef<HTMLElement>(null);\n const actionGroupRef = useRef<HTMLDivElement>(null);\n const copyFeedbackTimeoutRef = useRef<number>();\n const isActionGroupSticky = useStickyActionState(rootRef, actionGroupRef);\n const [hasCopied, setHasCopied] = useState(false);\n\n const colorScheme = useColorScheme();\n const colorMode =\n colorScheme?.theme.colorMode === 'dark'\n ? CopyableContentColorMode.Dark\n : CopyableContentColorMode.Light;\n\n const defaultCopyButtonText = useTextstringValue({\n textstring: ttsToITextString(textStrings.components.copyableContent.copy),\n });\n const defaultCopyFailedMessage = useTextstringValue({\n textstring: ttsToITextString(textStrings.components.copyableContent.copyFailed),\n });\n const shareText = useTextstringValue({\n textstring: ttsToITextString(textStrings.components.copyableContent.share),\n });\n\n const html = useMemo(() => formatStringToHtml(content).html, [content]);\n\n useEffect(\n () => () => {\n window.clearTimeout(copyFeedbackTimeoutRef.current);\n },\n [],\n );\n\n const showCopyFeedback = useCallback(() => {\n window.clearTimeout(copyFeedbackTimeoutRef.current);\n setHasCopied(true);\n\n copyFeedbackTimeoutRef.current = window.setTimeout(() => {\n setHasCopied(false);\n }, COPY_FEEDBACK_DURATION);\n }, []);\n\n const handleCopy = useCallback(async () => {\n try {\n await copyableContentToClipboard(content);\n showCopyFeedback();\n } catch {\n void createDialog({\n showCloseIcon: true,\n text: copyFailedMessage ?? defaultCopyFailedMessage,\n toastType: ToastType.ERROR,\n type: DialogType.TOAST,\n }).open();\n }\n }, [content, defaultCopyFailedMessage, copyFailedMessage, showCopyFeedback]);\n\n return (\n <StyledCopyableContent\n $appearance={appearance}\n $colorMode={colorMode}\n className=\"copyable-content\"\n ref={rootRef}\n >\n <StyledCopyableContentActions ref={actionGroupRef}>\n <StyledCopyableContentActionGroup>\n <StyledCopyableContentButton\n $colorMode={colorMode}\n $isSticky={isActionGroupSticky}\n aria-label={defaultCopyButtonText}\n onClick={() => {\n void handleCopy();\n }}\n type=\"button\"\n >\n <Icon icons={hasCopied ? ['fa fa-check'] : ['fa-light fa-copy']} />\n </StyledCopyableContentButton>\n <SharingContextMenu\n link={content}\n shouldShowCallingCodeAction={false}\n shouldShowCopyAction={false}\n shouldUseDefaultTriggerStyles={false}\n >\n <StyledCopyableContentButton\n $colorMode={colorMode}\n $isSticky={isActionGroupSticky}\n aria-label={shareText}\n type=\"button\"\n >\n <Icon icons={['fa fa-share-nodes']} />\n </StyledCopyableContentButton>\n </SharingContextMenu>\n </StyledCopyableContentActionGroup>\n </StyledCopyableContentActions>\n {typeof collapsedHeight === 'number' ? (\n <StyledCopyableContentTruncation>\n <Truncation collapsedHeight={collapsedHeight}>\n <StyledCopyableContentBody\n $colorMode={colorMode}\n $shouldShowPadding={false}\n >\n {children ?? <div dangerouslySetInnerHTML={{ __html: html }} />}\n </StyledCopyableContentBody>\n </Truncation>\n </StyledCopyableContentTruncation>\n ) : (\n <StyledCopyableContentBody $colorMode={colorMode}>\n {children ?? <div dangerouslySetInnerHTML={{ __html: html }} />}\n </StyledCopyableContentBody>\n )}\n </StyledCopyableContent>\n );\n};\n\nCopyableContent.displayName = 'CopyableContent';\n\nexport default CopyableContent;\n\nexport { CopyableContentAppearance } from './CopyableContent.types';\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"CopyableContent.js","names":["_format","require","_textstring","_chaynsApi","_react","_interopRequireWildcard","_textStrings","_interopRequireDefault","_useStickyActionState","_ColorSchemeProvider","_SharingContextMenu","_Icon","_Truncation","_CopyableContent","_CopyableContent2","_copyableContentClipboard","e","__esModule","default","t","WeakMap","r","n","o","i","f","__proto__","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","COPY_FEEDBACK_DURATION","CopyableContent","appearance","CopyableContentAppearance","Default","content","children","copyFailedMessage","collapsedHeight","transformClipboardHtml","rootRef","useRef","actionGroupRef","copyFeedbackTimeoutRef","isActionGroupSticky","useStickyActionState","hasCopied","setHasCopied","useState","colorScheme","useColorScheme","colorMode","theme","CopyableContentColorMode","Dark","Light","defaultCopyButtonText","useTextstringValue","textstring","ttsToITextString","textStrings","components","copyableContent","copy","defaultCopyFailedMessage","copyFailed","shareText","share","html","useMemo","formatStringToHtml","useEffect","window","clearTimeout","current","showCopyFeedback","useCallback","setTimeout","handleCopy","copyableContentToClipboard","createDialog","showCloseIcon","text","toastType","ToastType","ERROR","type","DialogType","TOAST","open","createElement","StyledCopyableContent","$appearance","$colorMode","className","ref","StyledCopyableContentActions","StyledCopyableContentActionGroup","StyledCopyableContentButton","$isSticky","onClick","icons","link","shouldShowCallingCodeAction","shouldShowCopyAction","shouldUseDefaultTriggerStyles","StyledCopyableContentTruncation","StyledCopyableContentBody","$shouldShowPadding","dangerouslySetInnerHTML","__html","displayName","_default","exports"],"sources":["../../../../src/components/copyable-content/CopyableContent.tsx"],"sourcesContent":["import { formatStringToHtml } from '@chayns-components/format';\nimport { ttsToITextString, useTextstringValue } from '@chayns-components/textstring';\nimport { createDialog, DialogType, ToastType } from 'chayns-api';\nimport React, { FC, ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport textStrings from '../../constants/textStrings';\nimport { useStickyActionState } from '../../hooks/useStickyActionState';\nimport { useColorScheme } from '../color-scheme-provider/ColorSchemeProvider';\nimport SharingContextMenu from '../sharing-context-menu/SharingContextMenu';\nimport Icon from '../icon/Icon';\nimport Truncation from '../truncation/Truncation';\nimport {\n CopyableContentColorMode,\n StyledCopyableContent,\n StyledCopyableContentActionGroup,\n StyledCopyableContentActions,\n StyledCopyableContentBody,\n StyledCopyableContentButton,\n StyledCopyableContentTruncation,\n} from './CopyableContent.styles';\nimport { CopyableContentAppearance } from './CopyableContent.types';\nimport { copyableContentToClipboard } from './copyableContentClipboard';\n\nconst COPY_FEEDBACK_DURATION = 1500;\n\nexport type CopyableContentProps = {\n /**\n * Controls the visual surface of the content block.\n */\n appearance?: CopyableContentAppearance;\n /**\n * Markdown source used for rendering and clipboard data.\n */\n content: string;\n /**\n * Replaces only the visible rendered content and never the copied source.\n */\n children?: ReactNode;\n /**\n * Replaces the localized error message shown when copying fails.\n */\n copyFailedMessage?: string;\n /**\n * The height of the content in its collapsed state.\n */\n collapsedHeight?: number;\n /**\n * Transforms the generated HTML before it is written to the clipboard.\n */\n transformClipboardHtml?: (html: string) => string;\n};\n\nconst CopyableContent: FC<CopyableContentProps> = ({\n appearance = CopyableContentAppearance.Default,\n content,\n children,\n copyFailedMessage,\n collapsedHeight,\n transformClipboardHtml,\n}) => {\n const rootRef = useRef<HTMLElement>(null);\n const actionGroupRef = useRef<HTMLDivElement>(null);\n const copyFeedbackTimeoutRef = useRef<number>();\n const isActionGroupSticky = useStickyActionState(rootRef, actionGroupRef);\n const [hasCopied, setHasCopied] = useState(false);\n\n const colorScheme = useColorScheme();\n const colorMode =\n colorScheme?.theme.colorMode === 'dark'\n ? CopyableContentColorMode.Dark\n : CopyableContentColorMode.Light;\n\n const defaultCopyButtonText = useTextstringValue({\n textstring: ttsToITextString(textStrings.components.copyableContent.copy),\n });\n const defaultCopyFailedMessage = useTextstringValue({\n textstring: ttsToITextString(textStrings.components.copyableContent.copyFailed),\n });\n const shareText = useTextstringValue({\n textstring: ttsToITextString(textStrings.components.copyableContent.share),\n });\n\n const html = useMemo(() => formatStringToHtml(content).html, [content]);\n\n useEffect(\n () => () => {\n window.clearTimeout(copyFeedbackTimeoutRef.current);\n },\n [],\n );\n\n const showCopyFeedback = useCallback(() => {\n window.clearTimeout(copyFeedbackTimeoutRef.current);\n setHasCopied(true);\n\n copyFeedbackTimeoutRef.current = window.setTimeout(() => {\n setHasCopied(false);\n }, COPY_FEEDBACK_DURATION);\n }, []);\n\n const handleCopy = useCallback(async () => {\n try {\n await copyableContentToClipboard(content, transformClipboardHtml);\n showCopyFeedback();\n } catch {\n void createDialog({\n showCloseIcon: true,\n text: copyFailedMessage ?? defaultCopyFailedMessage,\n toastType: ToastType.ERROR,\n type: DialogType.TOAST,\n }).open();\n }\n }, [\n content,\n defaultCopyFailedMessage,\n copyFailedMessage,\n showCopyFeedback,\n transformClipboardHtml,\n ]);\n\n return (\n <StyledCopyableContent\n $appearance={appearance}\n $colorMode={colorMode}\n className=\"copyable-content\"\n ref={rootRef}\n >\n <StyledCopyableContentActions ref={actionGroupRef}>\n <StyledCopyableContentActionGroup>\n <StyledCopyableContentButton\n $colorMode={colorMode}\n $isSticky={isActionGroupSticky}\n aria-label={defaultCopyButtonText}\n onClick={() => {\n void handleCopy();\n }}\n type=\"button\"\n >\n <Icon icons={hasCopied ? ['fa fa-check'] : ['fa-light fa-copy']} />\n </StyledCopyableContentButton>\n <SharingContextMenu\n link={content}\n shouldShowCallingCodeAction={false}\n shouldShowCopyAction={false}\n shouldUseDefaultTriggerStyles={false}\n >\n <StyledCopyableContentButton\n $colorMode={colorMode}\n $isSticky={isActionGroupSticky}\n aria-label={shareText}\n type=\"button\"\n >\n <Icon icons={['fa fa-share-nodes']} />\n </StyledCopyableContentButton>\n </SharingContextMenu>\n </StyledCopyableContentActionGroup>\n </StyledCopyableContentActions>\n {typeof collapsedHeight === 'number' ? (\n <StyledCopyableContentTruncation>\n <Truncation collapsedHeight={collapsedHeight}>\n <StyledCopyableContentBody\n $colorMode={colorMode}\n $shouldShowPadding={false}\n >\n {children ?? <div dangerouslySetInnerHTML={{ __html: html }} />}\n </StyledCopyableContentBody>\n </Truncation>\n </StyledCopyableContentTruncation>\n ) : (\n <StyledCopyableContentBody $colorMode={colorMode}>\n {children ?? <div dangerouslySetInnerHTML={{ __html: html }} />}\n </StyledCopyableContentBody>\n )}\n </StyledCopyableContent>\n );\n};\n\nCopyableContent.displayName = 'CopyableContent';\n\nexport default CopyableContent;\n\nexport { CopyableContentAppearance } from './CopyableContent.types';\n"],"mappings":";;;;;;;;;;;;AAAA,IAAAA,OAAA,GAAAC,OAAA;AACA,IAAAC,WAAA,GAAAD,OAAA;AACA,IAAAE,UAAA,GAAAF,OAAA;AACA,IAAAG,MAAA,GAAAC,uBAAA,CAAAJ,OAAA;AACA,IAAAK,YAAA,GAAAC,sBAAA,CAAAN,OAAA;AACA,IAAAO,qBAAA,GAAAP,OAAA;AACA,IAAAQ,oBAAA,GAAAR,OAAA;AACA,IAAAS,mBAAA,GAAAH,sBAAA,CAAAN,OAAA;AACA,IAAAU,KAAA,GAAAJ,sBAAA,CAAAN,OAAA;AACA,IAAAW,WAAA,GAAAL,sBAAA,CAAAN,OAAA;AACA,IAAAY,gBAAA,GAAAZ,OAAA;AASA,IAAAa,iBAAA,GAAAb,OAAA;AACA,IAAAc,yBAAA,GAAAd,OAAA;AAAwE,SAAAM,uBAAAS,CAAA,WAAAA,CAAA,IAAAA,CAAA,CAAAC,UAAA,GAAAD,CAAA,KAAAE,OAAA,EAAAF,CAAA;AAAA,SAAAX,wBAAAW,CAAA,EAAAG,CAAA,6BAAAC,OAAA,MAAAC,CAAA,OAAAD,OAAA,IAAAE,CAAA,OAAAF,OAAA,YAAAf,uBAAA,YAAAA,CAAAW,CAAA,EAAAG,CAAA,SAAAA,CAAA,IAAAH,CAAA,IAAAA,CAAA,CAAAC,UAAA,SAAAD,CAAA,MAAAO,CAAA,EAAAC,CAAA,EAAAC,CAAA,KAAAC,SAAA,QAAAR,OAAA,EAAAF,CAAA,iBAAAA,CAAA,uBAAAA,CAAA,yBAAAA,CAAA,SAAAS,CAAA,MAAAF,CAAA,GAAAJ,CAAA,GAAAG,CAAA,GAAAD,CAAA,QAAAE,CAAA,CAAAI,GAAA,CAAAX,CAAA,UAAAO,CAAA,CAAAK,GAAA,CAAAZ,CAAA,GAAAO,CAAA,CAAAM,GAAA,CAAAb,CAAA,EAAAS,CAAA,gBAAAN,CAAA,IAAAH,CAAA,gBAAAG,CAAA,OAAAW,cAAA,CAAAC,IAAA,CAAAf,CAAA,EAAAG,CAAA,OAAAK,CAAA,IAAAD,CAAA,GAAAS,MAAA,CAAAC,cAAA,KAAAD,MAAA,CAAAE,wBAAA,CAAAlB,CAAA,EAAAG,CAAA,OAAAK,CAAA,CAAAI,GAAA,IAAAJ,CAAA,CAAAK,GAAA,IAAAN,CAAA,CAAAE,CAAA,EAAAN,CAAA,EAAAK,CAAA,IAAAC,CAAA,CAAAN,CAAA,IAAAH,CAAA,CAAAG,CAAA,WAAAM,CAAA,KAAAT,CAAA,EAAAG,CAAA;AAExE,MAAMgB,sBAAsB,GAAG,IAAI;AA6BnC,MAAMC,eAAyC,GAAGA,CAAC;EAC/CC,UAAU,GAAGC,2CAAyB,CAACC,OAAO;EAC9CC,OAAO;EACPC,QAAQ;EACRC,iBAAiB;EACjBC,eAAe;EACfC;AACJ,CAAC,KAAK;EACF,MAAMC,OAAO,GAAG,IAAAC,aAAM,EAAc,IAAI,CAAC;EACzC,MAAMC,cAAc,GAAG,IAAAD,aAAM,EAAiB,IAAI,CAAC;EACnD,MAAME,sBAAsB,GAAG,IAAAF,aAAM,EAAS,CAAC;EAC/C,MAAMG,mBAAmB,GAAG,IAAAC,0CAAoB,EAACL,OAAO,EAAEE,cAAc,CAAC;EACzE,MAAM,CAACI,SAAS,EAAEC,YAAY,CAAC,GAAG,IAAAC,eAAQ,EAAC,KAAK,CAAC;EAEjD,MAAMC,WAAW,GAAG,IAAAC,mCAAc,EAAC,CAAC;EACpC,MAAMC,SAAS,GACX,CAAAF,WAAW,aAAXA,WAAW,uBAAXA,WAAW,CAAEG,KAAK,CAACD,SAAS,MAAK,MAAM,GACjCE,yCAAwB,CAACC,IAAI,GAC7BD,yCAAwB,CAACE,KAAK;EAExC,MAAMC,qBAAqB,GAAG,IAAAC,8BAAkB,EAAC;IAC7CC,UAAU,EAAE,IAAAC,4BAAgB,EAACC,oBAAW,CAACC,UAAU,CAACC,eAAe,CAACC,IAAI;EAC5E,CAAC,CAAC;EACF,MAAMC,wBAAwB,GAAG,IAAAP,8BAAkB,EAAC;IAChDC,UAAU,EAAE,IAAAC,4BAAgB,EAACC,oBAAW,CAACC,UAAU,CAACC,eAAe,CAACG,UAAU;EAClF,CAAC,CAAC;EACF,MAAMC,SAAS,GAAG,IAAAT,8BAAkB,EAAC;IACjCC,UAAU,EAAE,IAAAC,4BAAgB,EAACC,oBAAW,CAACC,UAAU,CAACC,eAAe,CAACK,KAAK;EAC7E,CAAC,CAAC;EAEF,MAAMC,IAAI,GAAG,IAAAC,cAAO,EAAC,MAAM,IAAAC,0BAAkB,EAACnC,OAAO,CAAC,CAACiC,IAAI,EAAE,CAACjC,OAAO,CAAC,CAAC;EAEvE,IAAAoC,gBAAS,EACL,MAAM,MAAM;IACRC,MAAM,CAACC,YAAY,CAAC9B,sBAAsB,CAAC+B,OAAO,CAAC;EACvD,CAAC,EACD,EACJ,CAAC;EAED,MAAMC,gBAAgB,GAAG,IAAAC,kBAAW,EAAC,MAAM;IACvCJ,MAAM,CAACC,YAAY,CAAC9B,sBAAsB,CAAC+B,OAAO,CAAC;IACnD3B,YAAY,CAAC,IAAI,CAAC;IAElBJ,sBAAsB,CAAC+B,OAAO,GAAGF,MAAM,CAACK,UAAU,CAAC,MAAM;MACrD9B,YAAY,CAAC,KAAK,CAAC;IACvB,CAAC,EAAEjB,sBAAsB,CAAC;EAC9B,CAAC,EAAE,EAAE,CAAC;EAEN,MAAMgD,UAAU,GAAG,IAAAF,kBAAW,EAAC,YAAY;IACvC,IAAI;MACA,MAAM,IAAAG,oDAA0B,EAAC5C,OAAO,EAAEI,sBAAsB,CAAC;MACjEoC,gBAAgB,CAAC,CAAC;IACtB,CAAC,CAAC,MAAM;MACJ,KAAK,IAAAK,uBAAY,EAAC;QACdC,aAAa,EAAE,IAAI;QACnBC,IAAI,EAAE7C,iBAAiB,IAAI2B,wBAAwB;QACnDmB,SAAS,EAAEC,oBAAS,CAACC,KAAK;QAC1BC,IAAI,EAAEC,qBAAU,CAACC;MACrB,CAAC,CAAC,CAACC,IAAI,CAAC,CAAC;IACb;EACJ,CAAC,EAAE,CACCtD,OAAO,EACP6B,wBAAwB,EACxB3B,iBAAiB,EACjBsC,gBAAgB,EAChBpC,sBAAsB,CACzB,CAAC;EAEF,oBACIxC,MAAA,CAAAc,OAAA,CAAA6E,aAAA,CAAClF,gBAAA,CAAAmF,qBAAqB;IAClBC,WAAW,EAAE5D,UAAW;IACxB6D,UAAU,EAAE1C,SAAU;IACtB2C,SAAS,EAAC,kBAAkB;IAC5BC,GAAG,EAAEvD;EAAQ,gBAEbzC,MAAA,CAAAc,OAAA,CAAA6E,aAAA,CAAClF,gBAAA,CAAAwF,4BAA4B;IAACD,GAAG,EAAErD;EAAe,gBAC9C3C,MAAA,CAAAc,OAAA,CAAA6E,aAAA,CAAClF,gBAAA,CAAAyF,gCAAgC,qBAC7BlG,MAAA,CAAAc,OAAA,CAAA6E,aAAA,CAAClF,gBAAA,CAAA0F,2BAA2B;IACxBL,UAAU,EAAE1C,SAAU;IACtBgD,SAAS,EAAEvD,mBAAoB;IAC/B,cAAYY,qBAAsB;IAClC4C,OAAO,EAAEA,CAAA,KAAM;MACX,KAAKtB,UAAU,CAAC,CAAC;IACrB,CAAE;IACFQ,IAAI,EAAC;EAAQ,gBAEbvF,MAAA,CAAAc,OAAA,CAAA6E,aAAA,CAACpF,KAAA,CAAAO,OAAI;IAACwF,KAAK,EAAEvD,SAAS,GAAG,CAAC,aAAa,CAAC,GAAG,CAAC,kBAAkB;EAAE,CAAE,CACzC,CAAC,eAC9B/C,MAAA,CAAAc,OAAA,CAAA6E,aAAA,CAACrF,mBAAA,CAAAQ,OAAkB;IACfyF,IAAI,EAAEnE,OAAQ;IACdoE,2BAA2B,EAAE,KAAM;IACnCC,oBAAoB,EAAE,KAAM;IAC5BC,6BAA6B,EAAE;EAAM,gBAErC1G,MAAA,CAAAc,OAAA,CAAA6E,aAAA,CAAClF,gBAAA,CAAA0F,2BAA2B;IACxBL,UAAU,EAAE1C,SAAU;IACtBgD,SAAS,EAAEvD,mBAAoB;IAC/B,cAAYsB,SAAU;IACtBoB,IAAI,EAAC;EAAQ,gBAEbvF,MAAA,CAAAc,OAAA,CAAA6E,aAAA,CAACpF,KAAA,CAAAO,OAAI;IAACwF,KAAK,EAAE,CAAC,mBAAmB;EAAE,CAAE,CACZ,CACb,CACU,CACR,CAAC,EAC9B,OAAO/D,eAAe,KAAK,QAAQ,gBAChCvC,MAAA,CAAAc,OAAA,CAAA6E,aAAA,CAAClF,gBAAA,CAAAkG,+BAA+B,qBAC5B3G,MAAA,CAAAc,OAAA,CAAA6E,aAAA,CAACnF,WAAA,CAAAM,OAAU;IAACyB,eAAe,EAAEA;EAAgB,gBACzCvC,MAAA,CAAAc,OAAA,CAAA6E,aAAA,CAAClF,gBAAA,CAAAmG,yBAAyB;IACtBd,UAAU,EAAE1C,SAAU;IACtByD,kBAAkB,EAAE;EAAM,GAEzBxE,QAAQ,iBAAIrC,MAAA,CAAAc,OAAA,CAAA6E,aAAA;IAAKmB,uBAAuB,EAAE;MAAEC,MAAM,EAAE1C;IAAK;EAAE,CAAE,CACvC,CACnB,CACiB,CAAC,gBAElCrE,MAAA,CAAAc,OAAA,CAAA6E,aAAA,CAAClF,gBAAA,CAAAmG,yBAAyB;IAACd,UAAU,EAAE1C;EAAU,GAC5Cf,QAAQ,iBAAIrC,MAAA,CAAAc,OAAA,CAAA6E,aAAA;IAAKmB,uBAAuB,EAAE;MAAEC,MAAM,EAAE1C;IAAK;EAAE,CAAE,CACvC,CAEZ,CAAC;AAEhC,CAAC;AAEDrC,eAAe,CAACgF,WAAW,GAAG,iBAAiB;AAAC,IAAAC,QAAA,GAAAC,OAAA,CAAApG,OAAA,GAEjCkB,eAAe","ignoreList":[]}
|
|
@@ -93,6 +93,20 @@ const readBlob = blob => new Promise((resolve, reject) => {
|
|
|
93
93
|
const item = write.mock.calls[0][0][0];
|
|
94
94
|
await (0, _vitest.expect)(readBlob(await item.getType('text/plain'))).resolves.toBe('source markdown');
|
|
95
95
|
});
|
|
96
|
+
(0, _vitest.it)('passes the clipboard HTML transformer to the copy action', async () => {
|
|
97
|
+
const write = _vitest.vi.spyOn(navigator.clipboard, 'write').mockResolvedValue();
|
|
98
|
+
const transformClipboardHtml = _vitest.vi.fn(html => `${html}<p>Styled</p>`);
|
|
99
|
+
(0, _react.render)(/*#__PURE__*/_react2.default.createElement(_CopyableContent.default, {
|
|
100
|
+
content: "source markdown",
|
|
101
|
+
transformClipboardHtml: transformClipboardHtml
|
|
102
|
+
}));
|
|
103
|
+
_react.fireEvent.click(_react.screen.getByRole('button', {
|
|
104
|
+
name: 'Kopieren'
|
|
105
|
+
}));
|
|
106
|
+
await (0, _react.waitFor)(() => (0, _vitest.expect)(transformClipboardHtml).toHaveBeenCalledOnce());
|
|
107
|
+
const item = write.mock.calls[0][0][0];
|
|
108
|
+
await (0, _vitest.expect)(readBlob(await item.getType('text/html'))).resolves.toContain('<p>Styled</p>');
|
|
109
|
+
});
|
|
96
110
|
(0, _vitest.it)('shows a checkmark after a successful copy', async () => {
|
|
97
111
|
_vitest.vi.spyOn(navigator.clipboard, 'write').mockResolvedValue();
|
|
98
112
|
const {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CopyableContent.test.js","names":["_react","require","_react2","_interopRequireDefault","_vitest","_CopyableContent","e","__esModule","default","vi","mock","Textstring","textstring","fallback","TextstringProvider","children","ttsToITextString","useTextstringValue","resizeObserverCallbacks","ResizeObserverMock","constructor","callback","push","disconnect","observe","unobserve","stubGlobal","triggerResize","height","forEach","contentRect","icons","createElement","join","shouldShowCallingCodeAction","shouldShowCopyAction","importOriginal","createDialog","fn","open","readBlob","blob","Promise","resolve","reject","reader","FileReader","onerror","error","onload","result","readAsText","describe","it","render","content","expect","screen","getByRole","name","toBeInTheDocument","toHaveAttribute","document","querySelector","not","write","spyOn","navigator","clipboard","mockResolvedValue","fireEvent","click","waitFor","toHaveBeenCalled","item","calls","getType","resolves","toBe","container","collapsedHeight","Array","fill"],"sources":["../../../../src/components/copyable-content/CopyableContent.test.tsx"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-unsafe-call */\nimport { fireEvent, render, screen, waitFor } from '@testing-library/react';\nimport React from 'react';\nimport { describe, expect, it, vi } from 'vitest';\nimport CopyableContent from './CopyableContent';\n\nvi.mock('@chayns-components/textstring', () => ({\n Textstring: ({ textstring }: { textstring: { fallback: string } }) => textstring.fallback,\n TextstringProvider: ({ children }: { children: React.ReactNode }) => children,\n ttsToITextString: (textstring: { fallback: string }) => textstring,\n useTextstringValue: ({ textstring }: { textstring: { fallback: string } }) =>\n textstring.fallback,\n}));\n\ntype ResizeObserverCallback = (entries: ResizeObserverEntry[]) => void;\n\nconst resizeObserverCallbacks: ResizeObserverCallback[] = [];\n\nclass ResizeObserverMock {\n constructor(callback: ResizeObserverCallback) {\n resizeObserverCallbacks.push(callback);\n }\n\n disconnect() {}\n\n observe() {}\n\n unobserve() {}\n}\n\nvi.stubGlobal('ResizeObserver', ResizeObserverMock);\n\nconst triggerResize = (height: number) => {\n resizeObserverCallbacks.forEach((callback) => {\n callback([\n {\n contentRect: { height } as DOMRectReadOnly,\n } as ResizeObserverEntry,\n ]);\n });\n};\n\nvi.mock('../icon/Icon', () => ({\n default: ({ icons }: { icons: string[] }) => <span data-icons={icons.join(' ')} />,\n}));\n\nvi.mock('../sharing-context-menu/SharingContextMenu', () => ({\n default: ({\n children,\n shouldShowCallingCodeAction,\n shouldShowCopyAction,\n }: {\n children: React.ReactNode;\n shouldShowCallingCodeAction?: boolean;\n shouldShowCopyAction?: boolean;\n }) => (\n <div\n data-calling-code-action={shouldShowCallingCodeAction}\n data-copy-action={shouldShowCopyAction}\n >\n {children}\n </div>\n ),\n}));\n\nvi.mock('chayns-api', async (importOriginal) => ({\n ...(await importOriginal<typeof import('chayns-api')>()),\n createDialog: vi.fn(() => ({ open: vi.fn() })),\n}));\n\nconst readBlob = (blob: Blob) =>\n new Promise<string>((resolve, reject) => {\n const reader = new FileReader();\n reader.onerror = () => reject(reader.error);\n reader.onload = () => resolve(reader.result as string);\n reader.readAsText(blob);\n });\n\ndescribe('CopyableContent', () => {\n it('renders formatted markdown without active input HTML', () => {\n render(\n <CopyableContent\n content={\n '# Heading\\n\\n- Entry\\n\\n[Link](https://example.com)\\n<script onload=\"x\">bad</script>'\n }\n />,\n );\n\n expect(screen.getByRole('heading', { name: 'Heading' })).toBeInTheDocument();\n expect(screen.getByRole('link', { name: 'Link' })).toHaveAttribute(\n 'href',\n 'https://example.com',\n );\n expect(document.querySelector('script')).not.toBeInTheDocument();\n expect(document.querySelector('[onload]')).not.toBeInTheDocument();\n });\n\n it('copies content instead of custom children', async () => {\n const write = vi.spyOn(navigator.clipboard, 'write').mockResolvedValue();\n render(<CopyableContent content=\"source markdown\">Visible replacement</CopyableContent>);\n\n fireEvent.click(screen.getByRole('button', { name: 'Kopieren' }));\n\n await waitFor(() => expect(write).toHaveBeenCalled());\n const item = write.mock.calls[0][0][0] as unknown as ClipboardItem;\n await expect(readBlob(await item.getType('text/plain'))).resolves.toBe('source markdown');\n });\n\n it('shows a checkmark after a successful copy', async () => {\n vi.spyOn(navigator.clipboard, 'write').mockResolvedValue();\n const { container } = render(<CopyableContent content=\"source\" />);\n\n fireEvent.click(screen.getByRole('button', { name: 'Kopieren' }));\n\n await waitFor(() => {\n expect(container.querySelector('[data-icons=\"fa fa-check\"]')).toBeInTheDocument();\n });\n });\n\n it('offers sharing actions', () => {\n const { container } = render(<CopyableContent content=\"source\" />);\n\n expect(screen.getByRole('button', { name: 'Teilen' })).toBeInTheDocument();\n expect(container.querySelector('[data-copy-action=\"false\"]')).toBeInTheDocument();\n expect(container.querySelector('[data-calling-code-action=\"false\"]')).toBeInTheDocument();\n });\n\n it('supports collapsed content', async () => {\n render(\n <CopyableContent\n collapsedHeight={100}\n content={Array(20).fill('Long content').join('\\n\\n')}\n />,\n );\n\n triggerResize(240);\n\n await waitFor(() => {\n expect(screen.getByRole('button', { name: 'Mehr' })).toBeInTheDocument();\n });\n });\n});\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"CopyableContent.test.js","names":["_react","require","_react2","_interopRequireDefault","_vitest","_CopyableContent","e","__esModule","default","vi","mock","Textstring","textstring","fallback","TextstringProvider","children","ttsToITextString","useTextstringValue","resizeObserverCallbacks","ResizeObserverMock","constructor","callback","push","disconnect","observe","unobserve","stubGlobal","triggerResize","height","forEach","contentRect","icons","createElement","join","shouldShowCallingCodeAction","shouldShowCopyAction","importOriginal","createDialog","fn","open","readBlob","blob","Promise","resolve","reject","reader","FileReader","onerror","error","onload","result","readAsText","describe","it","render","content","expect","screen","getByRole","name","toBeInTheDocument","toHaveAttribute","document","querySelector","not","write","spyOn","navigator","clipboard","mockResolvedValue","fireEvent","click","waitFor","toHaveBeenCalled","item","calls","getType","resolves","toBe","transformClipboardHtml","html","toHaveBeenCalledOnce","toContain","container","collapsedHeight","Array","fill"],"sources":["../../../../src/components/copyable-content/CopyableContent.test.tsx"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-unsafe-call */\nimport { fireEvent, render, screen, waitFor } from '@testing-library/react';\nimport React from 'react';\nimport { describe, expect, it, vi } from 'vitest';\nimport CopyableContent from './CopyableContent';\n\nvi.mock('@chayns-components/textstring', () => ({\n Textstring: ({ textstring }: { textstring: { fallback: string } }) => textstring.fallback,\n TextstringProvider: ({ children }: { children: React.ReactNode }) => children,\n ttsToITextString: (textstring: { fallback: string }) => textstring,\n useTextstringValue: ({ textstring }: { textstring: { fallback: string } }) =>\n textstring.fallback,\n}));\n\ntype ResizeObserverCallback = (entries: ResizeObserverEntry[]) => void;\n\nconst resizeObserverCallbacks: ResizeObserverCallback[] = [];\n\nclass ResizeObserverMock {\n constructor(callback: ResizeObserverCallback) {\n resizeObserverCallbacks.push(callback);\n }\n\n disconnect() {}\n\n observe() {}\n\n unobserve() {}\n}\n\nvi.stubGlobal('ResizeObserver', ResizeObserverMock);\n\nconst triggerResize = (height: number) => {\n resizeObserverCallbacks.forEach((callback) => {\n callback([\n {\n contentRect: { height } as DOMRectReadOnly,\n } as ResizeObserverEntry,\n ]);\n });\n};\n\nvi.mock('../icon/Icon', () => ({\n default: ({ icons }: { icons: string[] }) => <span data-icons={icons.join(' ')} />,\n}));\n\nvi.mock('../sharing-context-menu/SharingContextMenu', () => ({\n default: ({\n children,\n shouldShowCallingCodeAction,\n shouldShowCopyAction,\n }: {\n children: React.ReactNode;\n shouldShowCallingCodeAction?: boolean;\n shouldShowCopyAction?: boolean;\n }) => (\n <div\n data-calling-code-action={shouldShowCallingCodeAction}\n data-copy-action={shouldShowCopyAction}\n >\n {children}\n </div>\n ),\n}));\n\nvi.mock('chayns-api', async (importOriginal) => ({\n ...(await importOriginal<typeof import('chayns-api')>()),\n createDialog: vi.fn(() => ({ open: vi.fn() })),\n}));\n\nconst readBlob = (blob: Blob) =>\n new Promise<string>((resolve, reject) => {\n const reader = new FileReader();\n reader.onerror = () => reject(reader.error);\n reader.onload = () => resolve(reader.result as string);\n reader.readAsText(blob);\n });\n\ndescribe('CopyableContent', () => {\n it('renders formatted markdown without active input HTML', () => {\n render(\n <CopyableContent\n content={\n '# Heading\\n\\n- Entry\\n\\n[Link](https://example.com)\\n<script onload=\"x\">bad</script>'\n }\n />,\n );\n\n expect(screen.getByRole('heading', { name: 'Heading' })).toBeInTheDocument();\n expect(screen.getByRole('link', { name: 'Link' })).toHaveAttribute(\n 'href',\n 'https://example.com',\n );\n expect(document.querySelector('script')).not.toBeInTheDocument();\n expect(document.querySelector('[onload]')).not.toBeInTheDocument();\n });\n\n it('copies content instead of custom children', async () => {\n const write = vi.spyOn(navigator.clipboard, 'write').mockResolvedValue();\n render(<CopyableContent content=\"source markdown\">Visible replacement</CopyableContent>);\n\n fireEvent.click(screen.getByRole('button', { name: 'Kopieren' }));\n\n await waitFor(() => expect(write).toHaveBeenCalled());\n const item = write.mock.calls[0][0][0] as unknown as ClipboardItem;\n await expect(readBlob(await item.getType('text/plain'))).resolves.toBe('source markdown');\n });\n\n it('passes the clipboard HTML transformer to the copy action', async () => {\n const write = vi.spyOn(navigator.clipboard, 'write').mockResolvedValue();\n const transformClipboardHtml = vi.fn((html: string) => `${html}<p>Styled</p>`);\n\n render(\n <CopyableContent\n content=\"source markdown\"\n transformClipboardHtml={transformClipboardHtml}\n />,\n );\n\n fireEvent.click(screen.getByRole('button', { name: 'Kopieren' }));\n\n await waitFor(() => expect(transformClipboardHtml).toHaveBeenCalledOnce());\n const item = write.mock.calls[0][0][0];\n\n await expect(readBlob(await item.getType('text/html'))).resolves.toContain('<p>Styled</p>');\n });\n\n it('shows a checkmark after a successful copy', async () => {\n vi.spyOn(navigator.clipboard, 'write').mockResolvedValue();\n const { container } = render(<CopyableContent content=\"source\" />);\n\n fireEvent.click(screen.getByRole('button', { name: 'Kopieren' }));\n\n await waitFor(() => {\n expect(container.querySelector('[data-icons=\"fa fa-check\"]')).toBeInTheDocument();\n });\n });\n\n it('offers sharing actions', () => {\n const { container } = render(<CopyableContent content=\"source\" />);\n\n expect(screen.getByRole('button', { name: 'Teilen' })).toBeInTheDocument();\n expect(container.querySelector('[data-copy-action=\"false\"]')).toBeInTheDocument();\n expect(container.querySelector('[data-calling-code-action=\"false\"]')).toBeInTheDocument();\n });\n\n it('supports collapsed content', async () => {\n render(\n <CopyableContent\n collapsedHeight={100}\n content={Array(20).fill('Long content').join('\\n\\n')}\n />,\n );\n\n triggerResize(240);\n\n await waitFor(() => {\n expect(screen.getByRole('button', { name: 'Mehr' })).toBeInTheDocument();\n });\n });\n});\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -18,13 +18,14 @@ const getPlainTextFromHtml = html => {
|
|
|
18
18
|
return ((_element$textContent = element.textContent) === null || _element$textContent === void 0 ? void 0 : _element$textContent.trim()) ?? '';
|
|
19
19
|
}).filter(Boolean).join('\n\n');
|
|
20
20
|
};
|
|
21
|
-
const createClipboardContent = source => {
|
|
21
|
+
const createClipboardContent = (source, transformHtml) => {
|
|
22
22
|
const {
|
|
23
23
|
html
|
|
24
24
|
} = (0, _format.formatStringToHtml)(source);
|
|
25
|
+
const clipboardHtml = (transformHtml === null || transformHtml === void 0 ? void 0 : transformHtml(html)) ?? html;
|
|
25
26
|
return {
|
|
26
|
-
html,
|
|
27
|
-
plainText: getPlainTextFromHtml(
|
|
27
|
+
html: clipboardHtml,
|
|
28
|
+
plainText: getPlainTextFromHtml(clipboardHtml)
|
|
28
29
|
};
|
|
29
30
|
};
|
|
30
31
|
const copyWithClipboardItem = async ({
|
|
@@ -56,8 +57,8 @@ const copyPlainText = async plainText => {
|
|
|
56
57
|
}
|
|
57
58
|
await navigator.clipboard.writeText(plainText);
|
|
58
59
|
};
|
|
59
|
-
const copyableContentToClipboard = async source => {
|
|
60
|
-
const clipboardContent = createClipboardContent(source);
|
|
60
|
+
const copyableContentToClipboard = async (source, transformHtml) => {
|
|
61
|
+
const clipboardContent = createClipboardContent(source, transformHtml);
|
|
61
62
|
if (!navigator.clipboard) {
|
|
62
63
|
throw new Error('Clipboard API is not available.');
|
|
63
64
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"copyableContentClipboard.js","names":["_format","require","getPlainTextFromHtml","html","document","DOMParser","parseFromString","Array","from","body","children","map","element","_element$textContent","tagName","item","_item$textContent","textContent","trim","join","filter","Boolean","createClipboardContent","source","formatStringToHtml","plainText","copyWithClipboardItem","clipboardData","ClipboardItem","Blob","type","navigator","clipboard","write","copyPlainText","writeText","copyableContentToClipboard","clipboardContent","Error","exports"],"sources":["../../../../src/components/copyable-content/copyableContentClipboard.ts"],"sourcesContent":["import { formatStringToHtml } from '@chayns-components/format';\n\ninterface ClipboardContent {\n html: string;\n plainText: string;\n}\n\nconst getPlainTextFromHtml = (html: string) => {\n const document = new DOMParser().parseFromString(html, 'text/html');\n\n return Array.from(document.body.children)\n .map((element) => {\n if (element.tagName === 'UL' || element.tagName === 'OL') {\n return Array.from(element.children)\n .map((item) => `• ${item.textContent?.trim() ?? ''}`)\n .join('\\n');\n }\n\n return element.textContent?.trim() ?? '';\n })\n .filter(Boolean)\n .join('\\n\\n');\n};\n\nconst createClipboardContent = (source: string): ClipboardContent => {\n const { html } = formatStringToHtml(source);\n return { html, plainText: getPlainTextFromHtml(
|
|
1
|
+
{"version":3,"file":"copyableContentClipboard.js","names":["_format","require","getPlainTextFromHtml","html","document","DOMParser","parseFromString","Array","from","body","children","map","element","_element$textContent","tagName","item","_item$textContent","textContent","trim","join","filter","Boolean","createClipboardContent","source","transformHtml","formatStringToHtml","clipboardHtml","plainText","copyWithClipboardItem","clipboardData","ClipboardItem","Blob","type","navigator","clipboard","write","copyPlainText","writeText","copyableContentToClipboard","clipboardContent","Error","exports"],"sources":["../../../../src/components/copyable-content/copyableContentClipboard.ts"],"sourcesContent":["import { formatStringToHtml } from '@chayns-components/format';\n\ninterface ClipboardContent {\n html: string;\n plainText: string;\n}\n\nconst getPlainTextFromHtml = (html: string) => {\n const document = new DOMParser().parseFromString(html, 'text/html');\n\n return Array.from(document.body.children)\n .map((element) => {\n if (element.tagName === 'UL' || element.tagName === 'OL') {\n return Array.from(element.children)\n .map((item) => `• ${item.textContent?.trim() ?? ''}`)\n .join('\\n');\n }\n\n return element.textContent?.trim() ?? '';\n })\n .filter(Boolean)\n .join('\\n\\n');\n};\n\nconst createClipboardContent = (\n source: string,\n transformHtml?: (html: string) => string,\n): ClipboardContent => {\n const { html } = formatStringToHtml(source);\n const clipboardHtml = transformHtml?.(html) ?? html;\n\n return { html: clipboardHtml, plainText: getPlainTextFromHtml(clipboardHtml) };\n};\n\nconst copyWithClipboardItem = async ({ html, plainText }: ClipboardContent) => {\n const clipboardData = new ClipboardItem({\n 'text/plain': new Blob([plainText], { type: 'text/plain' }),\n 'text/html': new Blob([html], { type: 'text/html' }),\n });\n\n await navigator.clipboard.write([clipboardData]);\n};\n\nconst copyPlainText = async (plainText: string) => {\n if (typeof ClipboardItem !== 'undefined') {\n try {\n await navigator.clipboard.write([\n new ClipboardItem({\n 'text/plain': new Blob([plainText], { type: 'text/plain' }),\n }),\n ]);\n return;\n } catch {\n // Use writeText as the final browser-compatible fallback.\n }\n }\n\n await navigator.clipboard.writeText(plainText);\n};\n\nexport const copyableContentToClipboard = async (\n source: string,\n transformHtml?: (html: string) => string,\n) => {\n const clipboardContent = createClipboardContent(source, transformHtml);\n\n if (!navigator.clipboard) {\n throw new Error('Clipboard API is not available.');\n }\n\n if (typeof ClipboardItem === 'undefined') {\n await copyPlainText(clipboardContent.plainText);\n return;\n }\n\n try {\n await copyWithClipboardItem(clipboardContent);\n } catch {\n // Use the plain-text fallback when the browser rejects rich MIME types.\n await copyPlainText(clipboardContent.plainText);\n }\n};\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -11,6 +11,12 @@ const readBlob = blob => new Promise((resolve, reject) => {
|
|
|
11
11
|
reader.readAsText(blob);
|
|
12
12
|
});
|
|
13
13
|
(0, _vitest.describe)('copyableContentToClipboard', () => {
|
|
14
|
+
(0, _vitest.it)('transforms the generated HTML before copying', async () => {
|
|
15
|
+
const write = _vitest.vi.spyOn(navigator.clipboard, 'write').mockResolvedValue();
|
|
16
|
+
await (0, _copyableContentClipboard.copyableContentToClipboard)('source', html => `${html}<p>Styled</p>`);
|
|
17
|
+
const clipboardItem = write.mock.calls[0][0][0];
|
|
18
|
+
await (0, _vitest.expect)(readBlob(await clipboardItem.getType('text/html'))).resolves.toContain('<p>Styled</p>');
|
|
19
|
+
});
|
|
14
20
|
(0, _vitest.afterEach)(() => {
|
|
15
21
|
_vitest.vi.restoreAllMocks();
|
|
16
22
|
_vitest.vi.unstubAllGlobals();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"copyableContentClipboard.test.js","names":["_format","require","_vitest","_copyableContentClipboard","source","readBlob","blob","Promise","resolve","reject","reader","FileReader","onerror","error","onload","result","readAsText","describe","
|
|
1
|
+
{"version":3,"file":"copyableContentClipboard.test.js","names":["_format","require","_vitest","_copyableContentClipboard","source","readBlob","blob","Promise","resolve","reject","reader","FileReader","onerror","error","onload","result","readAsText","describe","it","write","vi","spyOn","navigator","clipboard","mockResolvedValue","copyableContentToClipboard","html","clipboardItem","mock","calls","expect","getType","resolves","toContain","afterEach","restoreAllMocks","unstubAllGlobals","item","Object","keys","items","toEqual","toBe","formatStringToHtml","mockRejectedValueOnce","Error","mockResolvedValueOnce","writeText","toHaveBeenCalledTimes","not","toHaveBeenCalled","mockRejectedValue","toHaveBeenCalledWith","stubGlobal","undefined"],"sources":["../../../../src/components/copyable-content/copyableContentClipboard.test.ts"],"sourcesContent":["import { formatStringToHtml } from '@chayns-components/format';\nimport { afterEach, describe, expect, it, vi } from 'vitest';\nimport { copyableContentToClipboard } from './copyableContentClipboard';\n\nconst source = '# Heading\\n\\n[Link](https://example.com)';\n\nconst readBlob = (blob: Blob) =>\n new Promise<string>((resolve, reject) => {\n const reader = new FileReader();\n reader.onerror = () => reject(reader.error);\n reader.onload = () => resolve(reader.result as string);\n reader.readAsText(blob);\n });\n\ndescribe('copyableContentToClipboard', () => {\n it('transforms the generated HTML before copying', async () => {\n const write = vi.spyOn(navigator.clipboard, 'write').mockResolvedValue();\n\n await copyableContentToClipboard('source', (html) => `${html}<p>Styled</p>`);\n\n const clipboardItem = write.mock.calls[0][0][0];\n\n await expect(readBlob(await clipboardItem.getType('text/html'))).resolves.toContain(\n '<p>Styled</p>',\n );\n });\n\n afterEach(() => {\n vi.restoreAllMocks();\n vi.unstubAllGlobals();\n });\n\n it('writes readable plain text first and safe HTML second', async () => {\n const write = vi.spyOn(navigator.clipboard, 'write').mockResolvedValue();\n\n await copyableContentToClipboard(source);\n\n const item = write.mock.calls[0][0][0] as unknown as ClipboardItem & {\n items: Record<string, Blob>;\n };\n expect(Object.keys(item.items)).toEqual(['text/plain', 'text/html']);\n await expect(readBlob(await item.getType('text/plain'))).resolves.toBe('Heading\\n\\nLink');\n await expect(readBlob(await item.getType('text/html'))).resolves.toBe(\n formatStringToHtml(source).html,\n );\n });\n\n it('uses a plain ClipboardItem after a rejected rich write', async () => {\n const write = vi\n .spyOn(navigator.clipboard, 'write')\n .mockRejectedValueOnce(new Error('denied'))\n .mockResolvedValueOnce();\n const writeText = vi.spyOn(navigator.clipboard, 'writeText').mockResolvedValue();\n\n await copyableContentToClipboard(source);\n\n expect(write).toHaveBeenCalledTimes(2);\n expect(writeText).not.toHaveBeenCalled();\n const item = write.mock.calls[1][0][0] as unknown as ClipboardItem & {\n items: Record<string, Blob>;\n };\n expect(Object.keys(item.items)).toEqual(['text/plain']);\n await expect(readBlob(await item.getType('text/plain'))).resolves.toBe('Heading\\n\\nLink');\n });\n\n it('uses writeText after all ClipboardItem writes fail', async () => {\n vi.spyOn(navigator.clipboard, 'write').mockRejectedValue(new Error('denied'));\n const writeText = vi.spyOn(navigator.clipboard, 'writeText').mockResolvedValue();\n\n await copyableContentToClipboard(source);\n\n expect(writeText).toHaveBeenCalledWith('Heading\\n\\nLink');\n });\n\n it('uses writeText when ClipboardItem is unavailable', async () => {\n vi.stubGlobal('ClipboardItem', undefined);\n const writeText = vi.spyOn(navigator.clipboard, 'writeText').mockResolvedValue();\n\n await copyableContentToClipboard(source);\n\n expect(writeText).toHaveBeenCalledWith('Heading\\n\\nLink');\n });\n});\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -17,7 +17,8 @@ const CopyableContent = ({
|
|
|
17
17
|
content,
|
|
18
18
|
children,
|
|
19
19
|
copyFailedMessage,
|
|
20
|
-
collapsedHeight
|
|
20
|
+
collapsedHeight,
|
|
21
|
+
transformClipboardHtml
|
|
21
22
|
}) => {
|
|
22
23
|
const rootRef = useRef(null);
|
|
23
24
|
const actionGroupRef = useRef(null);
|
|
@@ -48,7 +49,7 @@ const CopyableContent = ({
|
|
|
48
49
|
}, []);
|
|
49
50
|
const handleCopy = useCallback(async () => {
|
|
50
51
|
try {
|
|
51
|
-
await copyableContentToClipboard(content);
|
|
52
|
+
await copyableContentToClipboard(content, transformClipboardHtml);
|
|
52
53
|
showCopyFeedback();
|
|
53
54
|
} catch {
|
|
54
55
|
void createDialog({
|
|
@@ -58,7 +59,7 @@ const CopyableContent = ({
|
|
|
58
59
|
type: DialogType.TOAST
|
|
59
60
|
}).open();
|
|
60
61
|
}
|
|
61
|
-
}, [content, defaultCopyFailedMessage, copyFailedMessage, showCopyFeedback]);
|
|
62
|
+
}, [content, defaultCopyFailedMessage, copyFailedMessage, showCopyFeedback, transformClipboardHtml]);
|
|
62
63
|
return /*#__PURE__*/React.createElement(StyledCopyableContent, {
|
|
63
64
|
$appearance: appearance,
|
|
64
65
|
$colorMode: colorMode,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CopyableContent.js","names":["formatStringToHtml","ttsToITextString","useTextstringValue","createDialog","DialogType","ToastType","React","useCallback","useEffect","useMemo","useRef","useState","textStrings","useStickyActionState","useColorScheme","SharingContextMenu","Icon","Truncation","CopyableContentColorMode","StyledCopyableContent","StyledCopyableContentActionGroup","StyledCopyableContentActions","StyledCopyableContentBody","StyledCopyableContentButton","StyledCopyableContentTruncation","CopyableContentAppearance","copyableContentToClipboard","COPY_FEEDBACK_DURATION","CopyableContent","appearance","Default","content","children","copyFailedMessage","collapsedHeight","rootRef","actionGroupRef","copyFeedbackTimeoutRef","isActionGroupSticky","hasCopied","setHasCopied","colorScheme","colorMode","theme","Dark","Light","defaultCopyButtonText","textstring","components","copyableContent","copy","defaultCopyFailedMessage","copyFailed","shareText","share","html","window","clearTimeout","current","showCopyFeedback","setTimeout","handleCopy","showCloseIcon","text","toastType","ERROR","type","TOAST","open","createElement","$appearance","$colorMode","className","ref","$isSticky","onClick","icons","link","shouldShowCallingCodeAction","shouldShowCopyAction","shouldUseDefaultTriggerStyles","$shouldShowPadding","dangerouslySetInnerHTML","__html","displayName"],"sources":["../../../../src/components/copyable-content/CopyableContent.tsx"],"sourcesContent":["import { formatStringToHtml } from '@chayns-components/format';\nimport { ttsToITextString, useTextstringValue } from '@chayns-components/textstring';\nimport { createDialog, DialogType, ToastType } from 'chayns-api';\nimport React, { FC, ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport textStrings from '../../constants/textStrings';\nimport { useStickyActionState } from '../../hooks/useStickyActionState';\nimport { useColorScheme } from '../color-scheme-provider/ColorSchemeProvider';\nimport SharingContextMenu from '../sharing-context-menu/SharingContextMenu';\nimport Icon from '../icon/Icon';\nimport Truncation from '../truncation/Truncation';\nimport {\n CopyableContentColorMode,\n StyledCopyableContent,\n StyledCopyableContentActionGroup,\n StyledCopyableContentActions,\n StyledCopyableContentBody,\n StyledCopyableContentButton,\n StyledCopyableContentTruncation,\n} from './CopyableContent.styles';\nimport { CopyableContentAppearance } from './CopyableContent.types';\nimport { copyableContentToClipboard } from './copyableContentClipboard';\n\nconst COPY_FEEDBACK_DURATION = 1500;\n\nexport type CopyableContentProps = {\n /**\n * Controls the visual surface of the content block.\n */\n appearance?: CopyableContentAppearance;\n /**\n * Markdown source used for rendering and clipboard data.\n */\n content: string;\n /**\n * Replaces only the visible rendered content and never the copied source.\n */\n children?: ReactNode;\n /**\n * Replaces the localized error message shown when copying fails.\n */\n copyFailedMessage?: string;\n /**\n * The height of the content in its collapsed state.\n */\n collapsedHeight?: number;\n};\n\nconst CopyableContent: FC<CopyableContentProps> = ({\n appearance = CopyableContentAppearance.Default,\n content,\n children,\n copyFailedMessage,\n collapsedHeight,\n}) => {\n const rootRef = useRef<HTMLElement>(null);\n const actionGroupRef = useRef<HTMLDivElement>(null);\n const copyFeedbackTimeoutRef = useRef<number>();\n const isActionGroupSticky = useStickyActionState(rootRef, actionGroupRef);\n const [hasCopied, setHasCopied] = useState(false);\n\n const colorScheme = useColorScheme();\n const colorMode =\n colorScheme?.theme.colorMode === 'dark'\n ? CopyableContentColorMode.Dark\n : CopyableContentColorMode.Light;\n\n const defaultCopyButtonText = useTextstringValue({\n textstring: ttsToITextString(textStrings.components.copyableContent.copy),\n });\n const defaultCopyFailedMessage = useTextstringValue({\n textstring: ttsToITextString(textStrings.components.copyableContent.copyFailed),\n });\n const shareText = useTextstringValue({\n textstring: ttsToITextString(textStrings.components.copyableContent.share),\n });\n\n const html = useMemo(() => formatStringToHtml(content).html, [content]);\n\n useEffect(\n () => () => {\n window.clearTimeout(copyFeedbackTimeoutRef.current);\n },\n [],\n );\n\n const showCopyFeedback = useCallback(() => {\n window.clearTimeout(copyFeedbackTimeoutRef.current);\n setHasCopied(true);\n\n copyFeedbackTimeoutRef.current = window.setTimeout(() => {\n setHasCopied(false);\n }, COPY_FEEDBACK_DURATION);\n }, []);\n\n const handleCopy = useCallback(async () => {\n try {\n await copyableContentToClipboard(content);\n showCopyFeedback();\n } catch {\n void createDialog({\n showCloseIcon: true,\n text: copyFailedMessage ?? defaultCopyFailedMessage,\n toastType: ToastType.ERROR,\n type: DialogType.TOAST,\n }).open();\n }\n }, [content, defaultCopyFailedMessage, copyFailedMessage, showCopyFeedback]);\n\n return (\n <StyledCopyableContent\n $appearance={appearance}\n $colorMode={colorMode}\n className=\"copyable-content\"\n ref={rootRef}\n >\n <StyledCopyableContentActions ref={actionGroupRef}>\n <StyledCopyableContentActionGroup>\n <StyledCopyableContentButton\n $colorMode={colorMode}\n $isSticky={isActionGroupSticky}\n aria-label={defaultCopyButtonText}\n onClick={() => {\n void handleCopy();\n }}\n type=\"button\"\n >\n <Icon icons={hasCopied ? ['fa fa-check'] : ['fa-light fa-copy']} />\n </StyledCopyableContentButton>\n <SharingContextMenu\n link={content}\n shouldShowCallingCodeAction={false}\n shouldShowCopyAction={false}\n shouldUseDefaultTriggerStyles={false}\n >\n <StyledCopyableContentButton\n $colorMode={colorMode}\n $isSticky={isActionGroupSticky}\n aria-label={shareText}\n type=\"button\"\n >\n <Icon icons={['fa fa-share-nodes']} />\n </StyledCopyableContentButton>\n </SharingContextMenu>\n </StyledCopyableContentActionGroup>\n </StyledCopyableContentActions>\n {typeof collapsedHeight === 'number' ? (\n <StyledCopyableContentTruncation>\n <Truncation collapsedHeight={collapsedHeight}>\n <StyledCopyableContentBody\n $colorMode={colorMode}\n $shouldShowPadding={false}\n >\n {children ?? <div dangerouslySetInnerHTML={{ __html: html }} />}\n </StyledCopyableContentBody>\n </Truncation>\n </StyledCopyableContentTruncation>\n ) : (\n <StyledCopyableContentBody $colorMode={colorMode}>\n {children ?? <div dangerouslySetInnerHTML={{ __html: html }} />}\n </StyledCopyableContentBody>\n )}\n </StyledCopyableContent>\n );\n};\n\nCopyableContent.displayName = 'CopyableContent';\n\nexport default CopyableContent;\n\nexport { CopyableContentAppearance } from './CopyableContent.types';\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"CopyableContent.js","names":["formatStringToHtml","ttsToITextString","useTextstringValue","createDialog","DialogType","ToastType","React","useCallback","useEffect","useMemo","useRef","useState","textStrings","useStickyActionState","useColorScheme","SharingContextMenu","Icon","Truncation","CopyableContentColorMode","StyledCopyableContent","StyledCopyableContentActionGroup","StyledCopyableContentActions","StyledCopyableContentBody","StyledCopyableContentButton","StyledCopyableContentTruncation","CopyableContentAppearance","copyableContentToClipboard","COPY_FEEDBACK_DURATION","CopyableContent","appearance","Default","content","children","copyFailedMessage","collapsedHeight","transformClipboardHtml","rootRef","actionGroupRef","copyFeedbackTimeoutRef","isActionGroupSticky","hasCopied","setHasCopied","colorScheme","colorMode","theme","Dark","Light","defaultCopyButtonText","textstring","components","copyableContent","copy","defaultCopyFailedMessage","copyFailed","shareText","share","html","window","clearTimeout","current","showCopyFeedback","setTimeout","handleCopy","showCloseIcon","text","toastType","ERROR","type","TOAST","open","createElement","$appearance","$colorMode","className","ref","$isSticky","onClick","icons","link","shouldShowCallingCodeAction","shouldShowCopyAction","shouldUseDefaultTriggerStyles","$shouldShowPadding","dangerouslySetInnerHTML","__html","displayName"],"sources":["../../../../src/components/copyable-content/CopyableContent.tsx"],"sourcesContent":["import { formatStringToHtml } from '@chayns-components/format';\nimport { ttsToITextString, useTextstringValue } from '@chayns-components/textstring';\nimport { createDialog, DialogType, ToastType } from 'chayns-api';\nimport React, { FC, ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport textStrings from '../../constants/textStrings';\nimport { useStickyActionState } from '../../hooks/useStickyActionState';\nimport { useColorScheme } from '../color-scheme-provider/ColorSchemeProvider';\nimport SharingContextMenu from '../sharing-context-menu/SharingContextMenu';\nimport Icon from '../icon/Icon';\nimport Truncation from '../truncation/Truncation';\nimport {\n CopyableContentColorMode,\n StyledCopyableContent,\n StyledCopyableContentActionGroup,\n StyledCopyableContentActions,\n StyledCopyableContentBody,\n StyledCopyableContentButton,\n StyledCopyableContentTruncation,\n} from './CopyableContent.styles';\nimport { CopyableContentAppearance } from './CopyableContent.types';\nimport { copyableContentToClipboard } from './copyableContentClipboard';\n\nconst COPY_FEEDBACK_DURATION = 1500;\n\nexport type CopyableContentProps = {\n /**\n * Controls the visual surface of the content block.\n */\n appearance?: CopyableContentAppearance;\n /**\n * Markdown source used for rendering and clipboard data.\n */\n content: string;\n /**\n * Replaces only the visible rendered content and never the copied source.\n */\n children?: ReactNode;\n /**\n * Replaces the localized error message shown when copying fails.\n */\n copyFailedMessage?: string;\n /**\n * The height of the content in its collapsed state.\n */\n collapsedHeight?: number;\n /**\n * Transforms the generated HTML before it is written to the clipboard.\n */\n transformClipboardHtml?: (html: string) => string;\n};\n\nconst CopyableContent: FC<CopyableContentProps> = ({\n appearance = CopyableContentAppearance.Default,\n content,\n children,\n copyFailedMessage,\n collapsedHeight,\n transformClipboardHtml,\n}) => {\n const rootRef = useRef<HTMLElement>(null);\n const actionGroupRef = useRef<HTMLDivElement>(null);\n const copyFeedbackTimeoutRef = useRef<number>();\n const isActionGroupSticky = useStickyActionState(rootRef, actionGroupRef);\n const [hasCopied, setHasCopied] = useState(false);\n\n const colorScheme = useColorScheme();\n const colorMode =\n colorScheme?.theme.colorMode === 'dark'\n ? CopyableContentColorMode.Dark\n : CopyableContentColorMode.Light;\n\n const defaultCopyButtonText = useTextstringValue({\n textstring: ttsToITextString(textStrings.components.copyableContent.copy),\n });\n const defaultCopyFailedMessage = useTextstringValue({\n textstring: ttsToITextString(textStrings.components.copyableContent.copyFailed),\n });\n const shareText = useTextstringValue({\n textstring: ttsToITextString(textStrings.components.copyableContent.share),\n });\n\n const html = useMemo(() => formatStringToHtml(content).html, [content]);\n\n useEffect(\n () => () => {\n window.clearTimeout(copyFeedbackTimeoutRef.current);\n },\n [],\n );\n\n const showCopyFeedback = useCallback(() => {\n window.clearTimeout(copyFeedbackTimeoutRef.current);\n setHasCopied(true);\n\n copyFeedbackTimeoutRef.current = window.setTimeout(() => {\n setHasCopied(false);\n }, COPY_FEEDBACK_DURATION);\n }, []);\n\n const handleCopy = useCallback(async () => {\n try {\n await copyableContentToClipboard(content, transformClipboardHtml);\n showCopyFeedback();\n } catch {\n void createDialog({\n showCloseIcon: true,\n text: copyFailedMessage ?? defaultCopyFailedMessage,\n toastType: ToastType.ERROR,\n type: DialogType.TOAST,\n }).open();\n }\n }, [\n content,\n defaultCopyFailedMessage,\n copyFailedMessage,\n showCopyFeedback,\n transformClipboardHtml,\n ]);\n\n return (\n <StyledCopyableContent\n $appearance={appearance}\n $colorMode={colorMode}\n className=\"copyable-content\"\n ref={rootRef}\n >\n <StyledCopyableContentActions ref={actionGroupRef}>\n <StyledCopyableContentActionGroup>\n <StyledCopyableContentButton\n $colorMode={colorMode}\n $isSticky={isActionGroupSticky}\n aria-label={defaultCopyButtonText}\n onClick={() => {\n void handleCopy();\n }}\n type=\"button\"\n >\n <Icon icons={hasCopied ? ['fa fa-check'] : ['fa-light fa-copy']} />\n </StyledCopyableContentButton>\n <SharingContextMenu\n link={content}\n shouldShowCallingCodeAction={false}\n shouldShowCopyAction={false}\n shouldUseDefaultTriggerStyles={false}\n >\n <StyledCopyableContentButton\n $colorMode={colorMode}\n $isSticky={isActionGroupSticky}\n aria-label={shareText}\n type=\"button\"\n >\n <Icon icons={['fa fa-share-nodes']} />\n </StyledCopyableContentButton>\n </SharingContextMenu>\n </StyledCopyableContentActionGroup>\n </StyledCopyableContentActions>\n {typeof collapsedHeight === 'number' ? (\n <StyledCopyableContentTruncation>\n <Truncation collapsedHeight={collapsedHeight}>\n <StyledCopyableContentBody\n $colorMode={colorMode}\n $shouldShowPadding={false}\n >\n {children ?? <div dangerouslySetInnerHTML={{ __html: html }} />}\n </StyledCopyableContentBody>\n </Truncation>\n </StyledCopyableContentTruncation>\n ) : (\n <StyledCopyableContentBody $colorMode={colorMode}>\n {children ?? <div dangerouslySetInnerHTML={{ __html: html }} />}\n </StyledCopyableContentBody>\n )}\n </StyledCopyableContent>\n );\n};\n\nCopyableContent.displayName = 'CopyableContent';\n\nexport default CopyableContent;\n\nexport { CopyableContentAppearance } from './CopyableContent.types';\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -89,6 +89,20 @@ describe('CopyableContent', () => {
|
|
|
89
89
|
const item = write.mock.calls[0][0][0];
|
|
90
90
|
await expect(readBlob(await item.getType('text/plain'))).resolves.toBe('source markdown');
|
|
91
91
|
});
|
|
92
|
+
it('passes the clipboard HTML transformer to the copy action', async () => {
|
|
93
|
+
const write = vi.spyOn(navigator.clipboard, 'write').mockResolvedValue();
|
|
94
|
+
const transformClipboardHtml = vi.fn(html => `${html}<p>Styled</p>`);
|
|
95
|
+
render(/*#__PURE__*/React.createElement(CopyableContent, {
|
|
96
|
+
content: "source markdown",
|
|
97
|
+
transformClipboardHtml: transformClipboardHtml
|
|
98
|
+
}));
|
|
99
|
+
fireEvent.click(screen.getByRole('button', {
|
|
100
|
+
name: 'Kopieren'
|
|
101
|
+
}));
|
|
102
|
+
await waitFor(() => expect(transformClipboardHtml).toHaveBeenCalledOnce());
|
|
103
|
+
const item = write.mock.calls[0][0][0];
|
|
104
|
+
await expect(readBlob(await item.getType('text/html'))).resolves.toContain('<p>Styled</p>');
|
|
105
|
+
});
|
|
92
106
|
it('shows a checkmark after a successful copy', async () => {
|
|
93
107
|
vi.spyOn(navigator.clipboard, 'write').mockResolvedValue();
|
|
94
108
|
const {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CopyableContent.test.js","names":["fireEvent","render","screen","waitFor","React","describe","expect","it","vi","CopyableContent","mock","Textstring","textstring","fallback","TextstringProvider","children","ttsToITextString","useTextstringValue","resizeObserverCallbacks","ResizeObserverMock","constructor","callback","push","disconnect","observe","unobserve","stubGlobal","triggerResize","height","forEach","contentRect","default","icons","createElement","join","shouldShowCallingCodeAction","shouldShowCopyAction","importOriginal","createDialog","fn","open","readBlob","blob","Promise","resolve","reject","reader","FileReader","onerror","error","onload","result","readAsText","content","getByRole","name","toBeInTheDocument","toHaveAttribute","document","querySelector","not","write","spyOn","navigator","clipboard","mockResolvedValue","click","toHaveBeenCalled","item","calls","getType","resolves","toBe","container","collapsedHeight","Array","fill"],"sources":["../../../../src/components/copyable-content/CopyableContent.test.tsx"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-unsafe-call */\nimport { fireEvent, render, screen, waitFor } from '@testing-library/react';\nimport React from 'react';\nimport { describe, expect, it, vi } from 'vitest';\nimport CopyableContent from './CopyableContent';\n\nvi.mock('@chayns-components/textstring', () => ({\n Textstring: ({ textstring }: { textstring: { fallback: string } }) => textstring.fallback,\n TextstringProvider: ({ children }: { children: React.ReactNode }) => children,\n ttsToITextString: (textstring: { fallback: string }) => textstring,\n useTextstringValue: ({ textstring }: { textstring: { fallback: string } }) =>\n textstring.fallback,\n}));\n\ntype ResizeObserverCallback = (entries: ResizeObserverEntry[]) => void;\n\nconst resizeObserverCallbacks: ResizeObserverCallback[] = [];\n\nclass ResizeObserverMock {\n constructor(callback: ResizeObserverCallback) {\n resizeObserverCallbacks.push(callback);\n }\n\n disconnect() {}\n\n observe() {}\n\n unobserve() {}\n}\n\nvi.stubGlobal('ResizeObserver', ResizeObserverMock);\n\nconst triggerResize = (height: number) => {\n resizeObserverCallbacks.forEach((callback) => {\n callback([\n {\n contentRect: { height } as DOMRectReadOnly,\n } as ResizeObserverEntry,\n ]);\n });\n};\n\nvi.mock('../icon/Icon', () => ({\n default: ({ icons }: { icons: string[] }) => <span data-icons={icons.join(' ')} />,\n}));\n\nvi.mock('../sharing-context-menu/SharingContextMenu', () => ({\n default: ({\n children,\n shouldShowCallingCodeAction,\n shouldShowCopyAction,\n }: {\n children: React.ReactNode;\n shouldShowCallingCodeAction?: boolean;\n shouldShowCopyAction?: boolean;\n }) => (\n <div\n data-calling-code-action={shouldShowCallingCodeAction}\n data-copy-action={shouldShowCopyAction}\n >\n {children}\n </div>\n ),\n}));\n\nvi.mock('chayns-api', async (importOriginal) => ({\n ...(await importOriginal<typeof import('chayns-api')>()),\n createDialog: vi.fn(() => ({ open: vi.fn() })),\n}));\n\nconst readBlob = (blob: Blob) =>\n new Promise<string>((resolve, reject) => {\n const reader = new FileReader();\n reader.onerror = () => reject(reader.error);\n reader.onload = () => resolve(reader.result as string);\n reader.readAsText(blob);\n });\n\ndescribe('CopyableContent', () => {\n it('renders formatted markdown without active input HTML', () => {\n render(\n <CopyableContent\n content={\n '# Heading\\n\\n- Entry\\n\\n[Link](https://example.com)\\n<script onload=\"x\">bad</script>'\n }\n />,\n );\n\n expect(screen.getByRole('heading', { name: 'Heading' })).toBeInTheDocument();\n expect(screen.getByRole('link', { name: 'Link' })).toHaveAttribute(\n 'href',\n 'https://example.com',\n );\n expect(document.querySelector('script')).not.toBeInTheDocument();\n expect(document.querySelector('[onload]')).not.toBeInTheDocument();\n });\n\n it('copies content instead of custom children', async () => {\n const write = vi.spyOn(navigator.clipboard, 'write').mockResolvedValue();\n render(<CopyableContent content=\"source markdown\">Visible replacement</CopyableContent>);\n\n fireEvent.click(screen.getByRole('button', { name: 'Kopieren' }));\n\n await waitFor(() => expect(write).toHaveBeenCalled());\n const item = write.mock.calls[0][0][0] as unknown as ClipboardItem;\n await expect(readBlob(await item.getType('text/plain'))).resolves.toBe('source markdown');\n });\n\n it('shows a checkmark after a successful copy', async () => {\n vi.spyOn(navigator.clipboard, 'write').mockResolvedValue();\n const { container } = render(<CopyableContent content=\"source\" />);\n\n fireEvent.click(screen.getByRole('button', { name: 'Kopieren' }));\n\n await waitFor(() => {\n expect(container.querySelector('[data-icons=\"fa fa-check\"]')).toBeInTheDocument();\n });\n });\n\n it('offers sharing actions', () => {\n const { container } = render(<CopyableContent content=\"source\" />);\n\n expect(screen.getByRole('button', { name: 'Teilen' })).toBeInTheDocument();\n expect(container.querySelector('[data-copy-action=\"false\"]')).toBeInTheDocument();\n expect(container.querySelector('[data-calling-code-action=\"false\"]')).toBeInTheDocument();\n });\n\n it('supports collapsed content', async () => {\n render(\n <CopyableContent\n collapsedHeight={100}\n content={Array(20).fill('Long content').join('\\n\\n')}\n />,\n );\n\n triggerResize(240);\n\n await waitFor(() => {\n expect(screen.getByRole('button', { name: 'Mehr' })).toBeInTheDocument();\n });\n });\n});\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"CopyableContent.test.js","names":["fireEvent","render","screen","waitFor","React","describe","expect","it","vi","CopyableContent","mock","Textstring","textstring","fallback","TextstringProvider","children","ttsToITextString","useTextstringValue","resizeObserverCallbacks","ResizeObserverMock","constructor","callback","push","disconnect","observe","unobserve","stubGlobal","triggerResize","height","forEach","contentRect","default","icons","createElement","join","shouldShowCallingCodeAction","shouldShowCopyAction","importOriginal","createDialog","fn","open","readBlob","blob","Promise","resolve","reject","reader","FileReader","onerror","error","onload","result","readAsText","content","getByRole","name","toBeInTheDocument","toHaveAttribute","document","querySelector","not","write","spyOn","navigator","clipboard","mockResolvedValue","click","toHaveBeenCalled","item","calls","getType","resolves","toBe","transformClipboardHtml","html","toHaveBeenCalledOnce","toContain","container","collapsedHeight","Array","fill"],"sources":["../../../../src/components/copyable-content/CopyableContent.test.tsx"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-unsafe-call */\nimport { fireEvent, render, screen, waitFor } from '@testing-library/react';\nimport React from 'react';\nimport { describe, expect, it, vi } from 'vitest';\nimport CopyableContent from './CopyableContent';\n\nvi.mock('@chayns-components/textstring', () => ({\n Textstring: ({ textstring }: { textstring: { fallback: string } }) => textstring.fallback,\n TextstringProvider: ({ children }: { children: React.ReactNode }) => children,\n ttsToITextString: (textstring: { fallback: string }) => textstring,\n useTextstringValue: ({ textstring }: { textstring: { fallback: string } }) =>\n textstring.fallback,\n}));\n\ntype ResizeObserverCallback = (entries: ResizeObserverEntry[]) => void;\n\nconst resizeObserverCallbacks: ResizeObserverCallback[] = [];\n\nclass ResizeObserverMock {\n constructor(callback: ResizeObserverCallback) {\n resizeObserverCallbacks.push(callback);\n }\n\n disconnect() {}\n\n observe() {}\n\n unobserve() {}\n}\n\nvi.stubGlobal('ResizeObserver', ResizeObserverMock);\n\nconst triggerResize = (height: number) => {\n resizeObserverCallbacks.forEach((callback) => {\n callback([\n {\n contentRect: { height } as DOMRectReadOnly,\n } as ResizeObserverEntry,\n ]);\n });\n};\n\nvi.mock('../icon/Icon', () => ({\n default: ({ icons }: { icons: string[] }) => <span data-icons={icons.join(' ')} />,\n}));\n\nvi.mock('../sharing-context-menu/SharingContextMenu', () => ({\n default: ({\n children,\n shouldShowCallingCodeAction,\n shouldShowCopyAction,\n }: {\n children: React.ReactNode;\n shouldShowCallingCodeAction?: boolean;\n shouldShowCopyAction?: boolean;\n }) => (\n <div\n data-calling-code-action={shouldShowCallingCodeAction}\n data-copy-action={shouldShowCopyAction}\n >\n {children}\n </div>\n ),\n}));\n\nvi.mock('chayns-api', async (importOriginal) => ({\n ...(await importOriginal<typeof import('chayns-api')>()),\n createDialog: vi.fn(() => ({ open: vi.fn() })),\n}));\n\nconst readBlob = (blob: Blob) =>\n new Promise<string>((resolve, reject) => {\n const reader = new FileReader();\n reader.onerror = () => reject(reader.error);\n reader.onload = () => resolve(reader.result as string);\n reader.readAsText(blob);\n });\n\ndescribe('CopyableContent', () => {\n it('renders formatted markdown without active input HTML', () => {\n render(\n <CopyableContent\n content={\n '# Heading\\n\\n- Entry\\n\\n[Link](https://example.com)\\n<script onload=\"x\">bad</script>'\n }\n />,\n );\n\n expect(screen.getByRole('heading', { name: 'Heading' })).toBeInTheDocument();\n expect(screen.getByRole('link', { name: 'Link' })).toHaveAttribute(\n 'href',\n 'https://example.com',\n );\n expect(document.querySelector('script')).not.toBeInTheDocument();\n expect(document.querySelector('[onload]')).not.toBeInTheDocument();\n });\n\n it('copies content instead of custom children', async () => {\n const write = vi.spyOn(navigator.clipboard, 'write').mockResolvedValue();\n render(<CopyableContent content=\"source markdown\">Visible replacement</CopyableContent>);\n\n fireEvent.click(screen.getByRole('button', { name: 'Kopieren' }));\n\n await waitFor(() => expect(write).toHaveBeenCalled());\n const item = write.mock.calls[0][0][0] as unknown as ClipboardItem;\n await expect(readBlob(await item.getType('text/plain'))).resolves.toBe('source markdown');\n });\n\n it('passes the clipboard HTML transformer to the copy action', async () => {\n const write = vi.spyOn(navigator.clipboard, 'write').mockResolvedValue();\n const transformClipboardHtml = vi.fn((html: string) => `${html}<p>Styled</p>`);\n\n render(\n <CopyableContent\n content=\"source markdown\"\n transformClipboardHtml={transformClipboardHtml}\n />,\n );\n\n fireEvent.click(screen.getByRole('button', { name: 'Kopieren' }));\n\n await waitFor(() => expect(transformClipboardHtml).toHaveBeenCalledOnce());\n const item = write.mock.calls[0][0][0];\n\n await expect(readBlob(await item.getType('text/html'))).resolves.toContain('<p>Styled</p>');\n });\n\n it('shows a checkmark after a successful copy', async () => {\n vi.spyOn(navigator.clipboard, 'write').mockResolvedValue();\n const { container } = render(<CopyableContent content=\"source\" />);\n\n fireEvent.click(screen.getByRole('button', { name: 'Kopieren' }));\n\n await waitFor(() => {\n expect(container.querySelector('[data-icons=\"fa fa-check\"]')).toBeInTheDocument();\n });\n });\n\n it('offers sharing actions', () => {\n const { container } = render(<CopyableContent content=\"source\" />);\n\n expect(screen.getByRole('button', { name: 'Teilen' })).toBeInTheDocument();\n expect(container.querySelector('[data-copy-action=\"false\"]')).toBeInTheDocument();\n expect(container.querySelector('[data-calling-code-action=\"false\"]')).toBeInTheDocument();\n });\n\n it('supports collapsed content', async () => {\n render(\n <CopyableContent\n collapsedHeight={100}\n content={Array(20).fill('Long content').join('\\n\\n')}\n />,\n );\n\n triggerResize(240);\n\n await waitFor(() => {\n expect(screen.getByRole('button', { name: 'Mehr' })).toBeInTheDocument();\n });\n });\n});\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -8,13 +8,14 @@ const getPlainTextFromHtml = html => {
|
|
|
8
8
|
return element.textContent?.trim() ?? '';
|
|
9
9
|
}).filter(Boolean).join('\n\n');
|
|
10
10
|
};
|
|
11
|
-
const createClipboardContent = source => {
|
|
11
|
+
const createClipboardContent = (source, transformHtml) => {
|
|
12
12
|
const {
|
|
13
13
|
html
|
|
14
14
|
} = formatStringToHtml(source);
|
|
15
|
+
const clipboardHtml = transformHtml?.(html) ?? html;
|
|
15
16
|
return {
|
|
16
|
-
html,
|
|
17
|
-
plainText: getPlainTextFromHtml(
|
|
17
|
+
html: clipboardHtml,
|
|
18
|
+
plainText: getPlainTextFromHtml(clipboardHtml)
|
|
18
19
|
};
|
|
19
20
|
};
|
|
20
21
|
const copyWithClipboardItem = async ({
|
|
@@ -46,8 +47,8 @@ const copyPlainText = async plainText => {
|
|
|
46
47
|
}
|
|
47
48
|
await navigator.clipboard.writeText(plainText);
|
|
48
49
|
};
|
|
49
|
-
export const copyableContentToClipboard = async source => {
|
|
50
|
-
const clipboardContent = createClipboardContent(source);
|
|
50
|
+
export const copyableContentToClipboard = async (source, transformHtml) => {
|
|
51
|
+
const clipboardContent = createClipboardContent(source, transformHtml);
|
|
51
52
|
if (!navigator.clipboard) {
|
|
52
53
|
throw new Error('Clipboard API is not available.');
|
|
53
54
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"copyableContentClipboard.js","names":["formatStringToHtml","getPlainTextFromHtml","html","document","DOMParser","parseFromString","Array","from","body","children","map","element","tagName","item","textContent","trim","join","filter","Boolean","createClipboardContent","source","plainText","copyWithClipboardItem","clipboardData","ClipboardItem","Blob","type","navigator","clipboard","write","copyPlainText","writeText","copyableContentToClipboard","clipboardContent","Error"],"sources":["../../../../src/components/copyable-content/copyableContentClipboard.ts"],"sourcesContent":["import { formatStringToHtml } from '@chayns-components/format';\n\ninterface ClipboardContent {\n html: string;\n plainText: string;\n}\n\nconst getPlainTextFromHtml = (html: string) => {\n const document = new DOMParser().parseFromString(html, 'text/html');\n\n return Array.from(document.body.children)\n .map((element) => {\n if (element.tagName === 'UL' || element.tagName === 'OL') {\n return Array.from(element.children)\n .map((item) => `• ${item.textContent?.trim() ?? ''}`)\n .join('\\n');\n }\n\n return element.textContent?.trim() ?? '';\n })\n .filter(Boolean)\n .join('\\n\\n');\n};\n\nconst createClipboardContent = (source: string): ClipboardContent => {\n const { html } = formatStringToHtml(source);\n return { html, plainText: getPlainTextFromHtml(
|
|
1
|
+
{"version":3,"file":"copyableContentClipboard.js","names":["formatStringToHtml","getPlainTextFromHtml","html","document","DOMParser","parseFromString","Array","from","body","children","map","element","tagName","item","textContent","trim","join","filter","Boolean","createClipboardContent","source","transformHtml","clipboardHtml","plainText","copyWithClipboardItem","clipboardData","ClipboardItem","Blob","type","navigator","clipboard","write","copyPlainText","writeText","copyableContentToClipboard","clipboardContent","Error"],"sources":["../../../../src/components/copyable-content/copyableContentClipboard.ts"],"sourcesContent":["import { formatStringToHtml } from '@chayns-components/format';\n\ninterface ClipboardContent {\n html: string;\n plainText: string;\n}\n\nconst getPlainTextFromHtml = (html: string) => {\n const document = new DOMParser().parseFromString(html, 'text/html');\n\n return Array.from(document.body.children)\n .map((element) => {\n if (element.tagName === 'UL' || element.tagName === 'OL') {\n return Array.from(element.children)\n .map((item) => `• ${item.textContent?.trim() ?? ''}`)\n .join('\\n');\n }\n\n return element.textContent?.trim() ?? '';\n })\n .filter(Boolean)\n .join('\\n\\n');\n};\n\nconst createClipboardContent = (\n source: string,\n transformHtml?: (html: string) => string,\n): ClipboardContent => {\n const { html } = formatStringToHtml(source);\n const clipboardHtml = transformHtml?.(html) ?? html;\n\n return { html: clipboardHtml, plainText: getPlainTextFromHtml(clipboardHtml) };\n};\n\nconst copyWithClipboardItem = async ({ html, plainText }: ClipboardContent) => {\n const clipboardData = new ClipboardItem({\n 'text/plain': new Blob([plainText], { type: 'text/plain' }),\n 'text/html': new Blob([html], { type: 'text/html' }),\n });\n\n await navigator.clipboard.write([clipboardData]);\n};\n\nconst copyPlainText = async (plainText: string) => {\n if (typeof ClipboardItem !== 'undefined') {\n try {\n await navigator.clipboard.write([\n new ClipboardItem({\n 'text/plain': new Blob([plainText], { type: 'text/plain' }),\n }),\n ]);\n return;\n } catch {\n // Use writeText as the final browser-compatible fallback.\n }\n }\n\n await navigator.clipboard.writeText(plainText);\n};\n\nexport const copyableContentToClipboard = async (\n source: string,\n transformHtml?: (html: string) => string,\n) => {\n const clipboardContent = createClipboardContent(source, transformHtml);\n\n if (!navigator.clipboard) {\n throw new Error('Clipboard API is not available.');\n }\n\n if (typeof ClipboardItem === 'undefined') {\n await copyPlainText(clipboardContent.plainText);\n return;\n }\n\n try {\n await copyWithClipboardItem(clipboardContent);\n } catch {\n // Use the plain-text fallback when the browser rejects rich MIME types.\n await copyPlainText(clipboardContent.plainText);\n }\n};\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -9,6 +9,12 @@ const readBlob = blob => new Promise((resolve, reject) => {
|
|
|
9
9
|
reader.readAsText(blob);
|
|
10
10
|
});
|
|
11
11
|
describe('copyableContentToClipboard', () => {
|
|
12
|
+
it('transforms the generated HTML before copying', async () => {
|
|
13
|
+
const write = vi.spyOn(navigator.clipboard, 'write').mockResolvedValue();
|
|
14
|
+
await copyableContentToClipboard('source', html => `${html}<p>Styled</p>`);
|
|
15
|
+
const clipboardItem = write.mock.calls[0][0][0];
|
|
16
|
+
await expect(readBlob(await clipboardItem.getType('text/html'))).resolves.toContain('<p>Styled</p>');
|
|
17
|
+
});
|
|
12
18
|
afterEach(() => {
|
|
13
19
|
vi.restoreAllMocks();
|
|
14
20
|
vi.unstubAllGlobals();
|