Skip to content

Commit

Permalink
Markdown: add gray & rainbow to supported colors (streamlit#7106)
Browse files Browse the repository at this point in the history
Add gray & rainbow to extend supported markdown colors (currently blue, green, orange, red, violet)
  • Loading branch information
mayagbarnes authored and Your Name committed Mar 22, 2024
1 parent 9c1c1f8 commit 48b1915
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 88 deletions.
2 changes: 2 additions & 0 deletions e2e/scripts/st_markdown.py
Expand Up @@ -83,5 +83,7 @@
- :red[red]
- :violet[violet]
- :orange[orange]
- :gray[gray]
- :rainbow[rainbow]
"""
)
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Expand Up @@ -19,20 +19,17 @@ import "@testing-library/jest-dom"
import ReactMarkdown from "react-markdown"
import { screen, cleanup } from "@testing-library/react"

import { mount, render } from "@streamlit/lib/src/test_util"
import { render } from "@streamlit/lib/src/test_util"
import IsSidebarContext from "@streamlit/lib/src/components/core/IsSidebarContext"
import { colors } from "@streamlit/lib/src/theme/primitives/colors"

import StreamlitMarkdown, {
LinkWithTargetBlank,
createAnchorFromText,
HeadingWithAnchor,
CustomCodeTag,
CustomCodeTagProps,
} from "./StreamlitMarkdown"

import { StyledLinkIconContainer } from "./styled-components"

// Fixture Generator
const getMarkdownElement = (body: string): ReactElement => {
const components = {
Expand All @@ -58,103 +55,102 @@ describe("createAnchorFromText", () => {
describe("linkReference", () => {
it("renders a link with _blank target", () => {
const body = "Some random URL like [Streamlit](https://streamlit.io/)"
const wrapper = mount(getMarkdownElement(body))
expect(wrapper.find("a").prop("href")).toEqual("https://streamlit.io/")
expect(wrapper.find("a").prop("target")).toEqual("_blank")
render(getMarkdownElement(body))
expect(screen.getByText("Streamlit")).toHaveAttribute(
"href",
"https://streamlit.io/"
)
expect(screen.getByText("Streamlit")).toHaveAttribute("target", "_blank")
})

it("renders a link without title", () => {
const body =
"Everybody loves [The Internet Archive](https://archive.org/)."
const wrapper = mount(getMarkdownElement(body))

expect(wrapper.find("a").prop("href")).toEqual("https://archive.org/")
expect(wrapper.find("a").prop("title")).toBeUndefined()
render(getMarkdownElement(body))
const link = screen.getByText("The Internet Archive")
expect(link).toHaveAttribute("href", "https://archive.org/")
expect(link).not.toHaveAttribute("title")
})

it("renders a link containing a title", () => {
const body =
"My favorite search engine is " +
'[Duck Duck Go](https://duckduckgo.com/ "The best search engine for privacy").'
const wrapper = mount(getMarkdownElement(body))

expect(wrapper.find("a").prop("href")).toEqual("https://duckduckgo.com/")
expect(wrapper.find("a").prop("title")).toBe(
"The best search engine for privacy"
)
render(getMarkdownElement(body))
const link = screen.getByText("Duck Duck Go")
expect(link).toHaveAttribute("href", "https://duckduckgo.com/")
expect(link).toHaveAttribute("title", "The best search engine for privacy")
})

it("renders a link containing parentheses", () => {
const body =
"Here's a link containing parentheses [Yikes](http://msdn.microsoft.com/en-us/library/aa752574(VS.85).aspx)"
const wrapper = mount(getMarkdownElement(body))

expect(wrapper.find("a").prop("href")).toEqual(
render(getMarkdownElement(body))
const link = screen.getByText("Yikes")
expect(link instanceof HTMLAnchorElement).toBe(true)
expect(link).toHaveAttribute(
"href",
"http://msdn.microsoft.com/en-us/library/aa752574(VS.85).aspx"
)
})

it("does not render a link if only [text] and no (href)", () => {
const body = "Don't convert to a link if only [text] and missing (href)"
const wrapper = mount(getMarkdownElement(body))

expect(wrapper.text()).toEqual(
render(getMarkdownElement(body))
const element = screen.getByText("text", { exact: false })
expect(element).toHaveTextContent(
"Don't convert to a link if only [text] and missing (href)"
)
expect(wrapper.find("a").exists()).toBe(false)
expect(element instanceof HTMLAnchorElement).toBe(false)
})
})

describe("StreamlitMarkdown", () => {
it("renders header anchors when isSidebar is false", () => {
const source = "# header"
const wrapper = mount(
render(
<IsSidebarContext.Provider value={false}>
<StreamlitMarkdown source={source} allowHTML={false} />
</IsSidebarContext.Provider>
)
expect(wrapper.find(StyledLinkIconContainer).exists()).toBeTruthy()
expect(screen.getByTestId("StyledLinkIconContainer")).toBeInTheDocument()
})

it("passes props properly", () => {
const source =
"<a class='nav_item' href='//0.0.0.0:8501/?p=some_page' target='_self'>Some Page</a>"
const wrapper = mount(
<StreamlitMarkdown source={source} allowHTML={true} />
)

expect(wrapper.find("a").prop("href")).toEqual(
render(<StreamlitMarkdown source={source} allowHTML={true} />)
expect(screen.getByText("Some Page")).toHaveAttribute(
"href",
"//0.0.0.0:8501/?p=some_page"
)
expect(wrapper.find("a").prop("target")).toEqual("_self")
expect(screen.getByText("Some Page")).toHaveAttribute("target", "_self")
})

it("doesn't render header anchors when isSidebar is true", () => {
const source = "# header"
const wrapper = mount(
render(
<IsSidebarContext.Provider value={true}>
<StreamlitMarkdown source={source} allowHTML={false} />
</IsSidebarContext.Provider>
)
expect(wrapper.find(StyledLinkIconContainer).exists()).toBeFalsy()
expect(
screen.queryByTestId("StyledLinkIconContainer")
).not.toBeInTheDocument()
})

it("propagates header attributes to custom header", () => {
const source = '<h1 data-test="lol">alsdkjhflaf</h1>'
const wrapper = mount(<StreamlitMarkdown source={source} allowHTML />)
expect(
wrapper.find(HeadingWithAnchor).find("h1").prop("data-test")
).toEqual("lol")
render(<StreamlitMarkdown source={source} allowHTML />)
const h1 = screen.getByRole("heading")
expect(h1).toHaveAttribute("data-test", "lol")
})

it("displays captions correctly", () => {
const source = "hello this is a caption"
const wrapper = mount(
<StreamlitMarkdown allowHTML={false} source={source} isCaption />
)
expect(wrapper.find("StyledStreamlitMarkdown").text()).toEqual(
"hello this is a caption"
)
render(<StreamlitMarkdown allowHTML={false} source={source} isCaption />)
const caption = screen.getByTestId("stCaptionContainer")
expect(caption).toHaveTextContent("hello this is a caption")
})

// Valid Markdown - italics, bold, strikethrough, code, links, emojis, shortcodes
Expand Down Expand Up @@ -287,6 +283,8 @@ describe("StreamlitMarkdown", () => {
["green", colors.green90],
["violet", colors.purple80],
["orange", colors.orange100],
["gray", colors.gray80],
["rainbow", "transparent"],
])

colorMapping.forEach(function (style, color) {
Expand Down
Expand Up @@ -41,13 +41,7 @@ import remarkGfm from "remark-gfm"
import CodeBlock from "@streamlit/lib/src/components/elements/CodeBlock"
import IsSidebarContext from "@streamlit/lib/src/components/core/IsSidebarContext"
import ErrorBoundary from "@streamlit/lib/src/components/shared/ErrorBoundary"
import {
getMdBlue,
getMdGreen,
getMdOrange,
getMdRed,
getMdViolet,
} from "@streamlit/lib/src/theme"
import { getMarkdownTextColors } from "@streamlit/lib/src/theme"

import { LibContext } from "@streamlit/lib/src/components/core/LibContext"
import {
Expand Down Expand Up @@ -185,7 +179,7 @@ export const HeadingWithAnchor: FunctionComponent<HeadingWithAnchorProps> = ({
return React.createElement(
tag,
{ ...tagProps, ref, id: elementId },
<StyledLinkIconContainer>
<StyledLinkIconContainer data-testid="StyledLinkIconContainer">
{elementId && !hideAnchor && (
<StyledLinkIcon href={`#${elementId}`}>
<LinkIcon size="18" />
Expand Down Expand Up @@ -285,13 +279,19 @@ export function RenderedMarkdown({
...(overrideComponents || {}),
}
const theme = useTheme()
const { red, orange, yellow, green, blue, violet, purple, gray } =
getMarkdownTextColors(theme)
const colorMapping = new Map(
Object.entries({
red: getMdRed(theme),
blue: getMdBlue(theme),
green: getMdGreen(theme),
violet: getMdViolet(theme),
orange: getMdOrange(theme),
red: `color: ${red}`,
blue: `color: ${blue}`,
green: `color: ${green}`,
violet: `color: ${violet}`,
orange: `color: ${orange}`,
gray: `color: ${gray}`,
// Gradient from red, orange, yellow, green, blue, violet, purple
rainbow: `color: transparent; background-clip: text; -webkit-background-clip: text; background-image: linear-gradient(to right,
${red}, ${orange}, ${yellow}, ${green}, ${blue}, ${violet}, ${purple});`,
})
)
function remarkColoring() {
Expand All @@ -303,7 +303,7 @@ export function RenderedMarkdown({
const data = node.data || (node.data = {})
data.hName = "span"
data.hProperties = {
style: `color: ${colorMapping.get(nodeName)}`,
style: colorMapping.get(nodeName),
}
}
}
Expand Down
52 changes: 22 additions & 30 deletions frontend/lib/src/theme/utils.ts
Expand Up @@ -417,6 +417,28 @@ export function hasLightBackgroundColor(theme: EmotionTheme): boolean {
return getLuminance(theme.colors.bgColor) > 0.5
}

export function getMarkdownTextColors(theme: EmotionTheme): any {
const lightTheme = hasLightBackgroundColor(theme)
const red = lightTheme ? theme.colors.red80 : theme.colors.red70
const orange = lightTheme ? theme.colors.orange100 : theme.colors.orange60
const yellow = lightTheme ? theme.colors.yellow100 : theme.colors.yellow40
const green = lightTheme ? theme.colors.green90 : theme.colors.green60
const blue = lightTheme ? theme.colors.blue80 : theme.colors.blue50
const violet = lightTheme ? theme.colors.purple80 : theme.colors.purple50
const purple = lightTheme ? theme.colors.purple100 : theme.colors.purple80
const gray = lightTheme ? theme.colors.gray80 : theme.colors.gray70
return {
red: red,
orange: orange,
yellow: yellow,
green: green,
blue: blue,
violet: violet,
purple: purple,
gray: gray,
}
}

export function getGray70(theme: EmotionTheme): string {
return hasLightBackgroundColor(theme)
? theme.colors.gray70
Expand All @@ -435,36 +457,6 @@ export function getGray90(theme: EmotionTheme): string {
: theme.colors.gray10
}

export function getMdRed(theme: EmotionTheme): string {
return hasLightBackgroundColor(theme)
? theme.colors.red80
: theme.colors.red70
}

export function getMdBlue(theme: EmotionTheme): string {
return hasLightBackgroundColor(theme)
? theme.colors.blue80
: theme.colors.blue50
}

export function getMdGreen(theme: EmotionTheme): string {
return hasLightBackgroundColor(theme)
? theme.colors.green90
: theme.colors.green60
}

export function getMdViolet(theme: EmotionTheme): string {
return hasLightBackgroundColor(theme)
? theme.colors.purple80
: theme.colors.purple50
}

export function getMdOrange(theme: EmotionTheme): string {
return hasLightBackgroundColor(theme)
? theme.colors.orange100
: theme.colors.orange60
}

function getBlueArrayAsc(theme: EmotionTheme): string[] {
const { colors } = theme
return [
Expand Down

0 comments on commit 48b1915

Please sign in to comment.