import * as React from "react"; import { Text } from "@blueprintjs/core"; import styled from "styled-components"; import { ComponentProps } from "widgets/BaseComponent"; import Interweave from "interweave"; import { UrlMatcher, EmailMatcher } from "interweave-autolink"; import { DEFAULT_FONT_SIZE, FontStyleTypes, TextSize, } from "constants/WidgetConstants"; import { Icon, IconSize } from "design-system-old"; import { get } from "lodash"; import equal from "fast-deep-equal/es6"; import ModalComponent from "components/designSystems/appsmith/ModalComponent"; import { Color, Colors } from "constants/Colors"; import FontLoader from "./FontLoader"; import { fontSizeUtility } from "widgets/WidgetUtils"; import { OverflowTypes } from "../constants"; import LinkFilter from "./filters/LinkFilter"; export type TextAlign = "LEFT" | "CENTER" | "RIGHT" | "JUSTIFY"; const ELLIPSIS_HEIGHT = 15; export const TextContainer = styled.div` & { height: 100%; width: 100%; position: relative; } ul { list-style-type: disc; list-style-position: inside; } ol { list-style-type: decimal; list-style-position: inside; } ul ul, ol ul { list-style-type: circle; list-style-position: inside; margin-left: 15px; } ol ol, ul ol { list-style-type: lower-latin; list-style-position: inside; margin-left: 15px; } h1 { font-size: 2em; margin: 0.67em 0; } h2 { font-size: 1.5em; margin: 0.75em 0; } h3 { font-size: 1.17em; margin: 0.83em 0; } h5 { font-size: 0.83em; margin: 1.5em 0; } h6 { font-size: 0.75em; margin: 1.67em 0; } h1, h2, h3, h4, h5, h6 { font-weight: bold; } a { color: #106ba3; text-decoration: none; &:hover { text-decoration: underline; } } `; const StyledIcon = styled(Icon)<{ backgroundColor?: string }>` cursor: pointer; bottom: 0; left: 0; right: 0; height: ${ELLIPSIS_HEIGHT}px; background: ${(props) => props.backgroundColor ? props.backgroundColor : "transparent"}; `; type StyledTextProps = React.PropsWithChildren<{ overflow: OverflowTypes; isTruncated: boolean; textAlign: string; backgroundColor?: string; textColor?: string; fontStyle?: string; fontSize?: TextSize; }>; export const StyledText = styled(Text)` height: ${(props) => props.overflow === OverflowTypes.TRUNCATE ? `calc(100% - ${ELLIPSIS_HEIGHT}px)` : "100%"}; overflow-x: hidden; overflow-y: ${(props) => props.overflow !== OverflowTypes.SCROLL || props.overflow === OverflowTypes.TRUNCATE.valueOf() ? "hidden" : "auto"}; text-overflow: ellipsis; && div { display: block; width: 100%; } display: flex; width: 100%; justify-content: flex-start; flex-direction: ${(props) => (props.isTruncated ? "column" : "unset")}; align-items: ${(props) => props.overflow === OverflowTypes.SCROLL || props.overflow === OverflowTypes.TRUNCATE ? "flex-start" : "center"}; background: ${(props) => props?.backgroundColor}; color: ${(props) => props?.textColor}; font-style: ${(props) => props?.fontStyle?.includes(FontStyleTypes.ITALIC) ? "italic" : ""}; text-decoration: ${(props) => props?.fontStyle?.includes(FontStyleTypes.UNDERLINE) ? "underline" : ""}; font-weight: ${(props) => props?.fontStyle?.includes(FontStyleTypes.BOLD) ? "bold" : "normal"}; font-size: ${({ fontSize }) => fontSizeUtility(fontSize) || DEFAULT_FONT_SIZE}; word-break: break-word; span { width: 100%; line-height: 1.2; white-space: pre-wrap; text-align: ${(props) => props.textAlign.toLowerCase()}; } `; const ModalContent = styled.div<{ backgroundColor?: string; }>` background: ${(props) => props?.backgroundColor || Colors.WHITE}; padding: 24px; padding-top: 16px; `; const Heading = styled.div` display: flex; justify-content: space-between; align-items: center; .title { font-weight: 500; font-size: 20px; line-height: 24px; letter-spacing: -0.24px; color: ${Colors.GREY_10}; } .icon > svg > path { stroke: ${Colors.GREY_9}; } `; const Content = styled.div<{ fontSize?: TextSize; fontStyle?: string; textAlign: string; textColor?: string; }>` padding-top: 16px; color: ${(props) => props?.textColor}; max-height: 70vh; overflow: auto; overflow-wrap: break-word; text-align: ${(props) => props.textAlign.toLowerCase()}; font-style: ${(props) => props?.fontStyle?.includes(FontStyleTypes.ITALIC) ? "italic" : ""}; text-decoration: ${(props) => props?.fontStyle?.includes(FontStyleTypes.UNDERLINE) ? "underline" : ""}; font-weight: ${(props) => props?.fontStyle?.includes(FontStyleTypes.BOLD) ? "bold" : "normal"}; font-size: ${({ fontSize }) => fontSizeUtility(fontSize) || DEFAULT_FONT_SIZE}; `; export interface TextComponentProps extends ComponentProps { accentColor: string; text?: string; textAlign: TextAlign; ellipsize?: boolean; fontSize?: TextSize; fontFamily: string; isLoading: boolean; backgroundColor?: string; textColor?: string; fontStyle?: string; disableLink: boolean; truncateButtonColor?: string; borderColor?: Color; borderWidth?: number; overflow: OverflowTypes; // helpers to detect and re-calculate content width bottomRow?: number; leftColumn?: number; rightColumn?: number; topRow?: number; } type State = { isTruncated: boolean; showModal: boolean; }; type TextRef = React.Ref | undefined; class TextComponent extends React.Component { state = { isTruncated: false, showModal: false, }; textRef = React.createRef() as TextRef; getTruncate = (element: any) => { const { isTruncated } = this.state; // add ELLIPSIS_HEIGHT and check content content is overflowing or not return ( element.scrollHeight > element.offsetHeight + (isTruncated ? ELLIPSIS_HEIGHT : 0) ); }; componentDidMount = () => { const textRef = get(this.textRef, "current.textRef"); if (textRef && this.props.overflow === OverflowTypes.TRUNCATE) { const isTruncated = this.getTruncate(textRef); this.setState({ isTruncated }); } }; componentDidUpdate = (prevProps: TextComponentProps) => { if (!equal(prevProps, this.props)) { if (this.props.overflow === OverflowTypes.TRUNCATE) { const textRef = get(this.textRef, "current.textRef"); if (textRef) { const isTruncated = this.getTruncate(textRef); this.setState({ isTruncated }); } } else if ( prevProps.overflow === OverflowTypes.TRUNCATE && this.props.overflow !== OverflowTypes.TRUNCATE.valueOf() ) { this.setState({ isTruncated: false }); } } }; handleModelOpen = () => { this.setState({ showModal: true }); }; handleModelClose = () => { this.setState({ showModal: false }); }; render() { const { accentColor, backgroundColor, disableLink, ellipsize, fontSize, fontStyle, overflow, text, textAlign, textColor, truncateButtonColor, } = this.props; return ( <> {this.state.isTruncated && ( )}
Show More
); } } export default TextComponent;