diff --git a/e2e/scripts/st_header.py b/e2e/scripts/st_header.py
index 208c3935654f..5c601b905339 100644
--- a/e2e/scripts/st_header.py
+++ b/e2e/scripts/st_header.py
@@ -15,4 +15,3 @@
import streamlit as st
st.header("This header is awesome!")
-st.header("This header is awesome too!", anchor="awesome-header")
diff --git a/e2e/scripts/st_markdown.py b/e2e/scripts/st_markdown.py
index 626ef9b2fe60..3d7a1853d06c 100644
--- a/e2e/scripts/st_markdown.py
+++ b/e2e/scripts/st_markdown.py
@@ -35,7 +35,3 @@
$$
"""
)
-
-st.markdown("# Some header 1")
-st.markdown("## Some header 2")
-st.markdown("### Some header 3")
diff --git a/e2e/scripts/st_subheader.py b/e2e/scripts/st_subheader.py
index dc800a50229b..ac25ea566621 100644
--- a/e2e/scripts/st_subheader.py
+++ b/e2e/scripts/st_subheader.py
@@ -15,4 +15,3 @@
import streamlit as st
st.subheader("This subheader is awesome!")
-st.subheader("This subheader is awesome too!", anchor="awesome-subheader")
diff --git a/e2e/scripts/st_title.py b/e2e/scripts/st_title.py
index dbea889bcde6..6022610e9948 100644
--- a/e2e/scripts/st_title.py
+++ b/e2e/scripts/st_title.py
@@ -15,4 +15,3 @@
import streamlit as st
st.title("This title is awesome!")
-st.title("This title is awesome too!", anchor="awesome-title")
diff --git a/e2e/specs/st_header.spec.js b/e2e/specs/st_header.spec.js
index 0c7257ed9eb3..b7990b268cb7 100644
--- a/e2e/specs/st_header.spec.js
+++ b/e2e/specs/st_header.spec.js
@@ -20,21 +20,10 @@ describe("st.header", () => {
cy.visit("http://localhost:3000/");
});
- it("displays correct number of elements", () => {
- cy.get(".element-container .stMarkdown h2").should("have.length", 2);
- });
-
it("displays a header", () => {
- cy.get(".element-container .stMarkdown h2").then(els => {
- expect(els[0].textContent).to.eq("This header is awesome!");
- expect(els[1].textContent).to.eq("This header is awesome too!");
- });
- });
-
- it("displays headers with anchors", () => {
- cy.get(".element-container .stMarkdown h2").then(els => {
- cy.wrap(els[0]).should("have.attr", "id", "this-header-is-awesome");
- cy.wrap(els[1]).should("have.attr", "id", "awesome-header");
- });
+ cy.get(".element-container .stMarkdown h2").should(
+ "contain",
+ "This header is awesome!"
+ );
});
});
diff --git a/e2e/specs/st_markdown.spec.js b/e2e/specs/st_markdown.spec.js
index 218c77588946..3e588214a400 100644
--- a/e2e/specs/st_markdown.spec.js
+++ b/e2e/specs/st_markdown.spec.js
@@ -20,11 +20,8 @@ describe("st.markdown", () => {
cy.visit("http://localhost:3000/");
});
- it("displays correct number of elements", () => {
- cy.get(".element-container .stMarkdown").should("have.length", 11);
- });
-
it("displays markdown", () => {
+ cy.get(".element-container .stMarkdown").should("have.length", 8);
cy.get(".element-container .stMarkdown").then(els => {
expect(els[0].textContent).to.eq("This markdown is awesome! 😎");
expect(els[1].textContent).to.eq("This HTML tag is escaped!");
@@ -36,30 +33,14 @@ describe("st.markdown", () => {
expect(els[7].textContent).to.eq(
"ax2+bx+c=0ax^2 + bx + c = 0ax2+bx+c=0"
);
- expect(els[8].textContent).to.eq("Some header 1");
- expect(els[9].textContent).to.eq("Some header 2");
- expect(els[10].textContent).to.eq("Some header 3");
cy.wrap(els[3])
.find("a")
.should("not.exist");
+
cy.wrap(els[4])
.find("a")
.should("have.attr", "href");
});
});
-
- it("displays headers with anchors", () => {
- cy.get(".element-container .stMarkdown").then(els => {
- cy.wrap(els[8])
- .find("h1")
- .should("have.attr", "id", "some-header-1");
- cy.wrap(els[9])
- .find("h2")
- .should("have.attr", "id", "some-header-2");
- cy.wrap(els[10])
- .find("h3")
- .should("have.attr", "id", "some-header-3");
- });
- });
});
diff --git a/e2e/specs/st_subheader.spec.js b/e2e/specs/st_subheader.spec.js
index 1ca61c7657d0..de6b03ea6d02 100644
--- a/e2e/specs/st_subheader.spec.js
+++ b/e2e/specs/st_subheader.spec.js
@@ -20,21 +20,10 @@ describe("st.subheader", () => {
cy.visit("http://localhost:3000/");
});
- it("displays correct number of elements", () => {
- cy.get(".element-container .stMarkdown h3").should("have.length", 2);
- });
-
it("displays a subheader", () => {
- cy.get(".element-container .stMarkdown h3").then(els => {
- expect(els[0].textContent).to.eq("This subheader is awesome!");
- expect(els[1].textContent).to.eq("This subheader is awesome too!");
- });
- });
-
- it("displays subheaders with anchors", () => {
- cy.get(".element-container .stMarkdown h3").then(els => {
- cy.wrap(els[0]).should("have.attr", "id", "this-subheader-is-awesome");
- cy.wrap(els[1]).should("have.attr", "id", "awesome-subheader");
- });
+ cy.get(".element-container .stMarkdown h3").should(
+ "contain",
+ "This subheader is awesome!"
+ );
});
});
diff --git a/e2e/specs/st_title.spec.js b/e2e/specs/st_title.spec.js
index f69302383de8..1d72b0d95e14 100644
--- a/e2e/specs/st_title.spec.js
+++ b/e2e/specs/st_title.spec.js
@@ -20,21 +20,10 @@ describe("st.title", () => {
cy.visit("http://localhost:3000/");
});
- it("displays correct number of elements", () => {
- cy.get(".element-container .stMarkdown h1").should("have.length", 2);
- });
-
it("displays a title", () => {
- cy.get(".element-container .stMarkdown h1").then(els => {
- expect(els[0].textContent).to.eq("This title is awesome!");
- expect(els[1].textContent).to.eq("This title is awesome too!");
- });
- });
-
- it("displays title with anchors", () => {
- cy.get(".element-container .stMarkdown h1").then(els => {
- cy.wrap(els[0]).should("have.attr", "id", "this-title-is-awesome");
- cy.wrap(els[1]).should("have.attr", "id", "awesome-title");
- });
+ cy.get(".element-container .stMarkdown h1").should(
+ "contain",
+ "This title is awesome!"
+ );
});
});
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 524bbb2a12f5..a4f9c6b6b611 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -58,7 +58,6 @@ import {
SessionState,
Config,
} from "autogen/proto"
-import { without, concat } from "lodash"
import { RERUN_PROMPT_MODAL_DIALOG } from "lib/baseconsts"
import { SessionInfo } from "lib/SessionInfo"
@@ -102,7 +101,6 @@ interface State {
layout: PageConfig.Layout
initialSidebarState: PageConfig.SidebarState
allowRunOnSave: boolean
- reportFinishedHandlers: (() => void)[]
deployParams?: IDeployParams | null
}
@@ -160,7 +158,6 @@ export class App extends PureComponent {
layout: PageConfig.Layout.CENTERED,
initialSidebarState: PageConfig.SidebarState.AUTO,
allowRunOnSave: true,
- reportFinishedHandlers: [],
deployParams: null,
}
@@ -557,12 +554,6 @@ export class App extends PureComponent {
*/
handleReportFinished(status: ForwardMsg.ReportFinishedStatus): void {
if (status === ForwardMsg.ReportFinishedStatus.FINISHED_SUCCESSFULLY) {
- // Notify any subscribers of this event (and do it on the next cycle of
- // the event loop)
- window.setTimeout(() => {
- this.state.reportFinishedHandlers.map(handler => handler())
- }, 0)
-
// Clear any stale elements left over from the previous run.
// (We don't do this if our script had a compilation error and didn't
// finish successfully.)
@@ -904,18 +895,6 @@ export class App extends PureComponent {
this.setState({ isFullScreen })
}
- addReportFinishedHandler = (func: () => void): void => {
- this.setState({
- reportFinishedHandlers: concat(this.state.reportFinishedHandlers, func),
- })
- }
-
- removeReportFinishedHandler = (func: () => void): void => {
- this.setState({
- reportFinishedHandlers: without(this.state.reportFinishedHandlers, func),
- })
- }
-
render(): JSX.Element {
const {
allowRunOnSave,
@@ -956,8 +935,6 @@ export class App extends PureComponent {
embedded: isEmbeddedInIFrame(),
isFullScreen,
setFullScreen: this.handleFullScreen,
- addReportFinishedHandler: this.addReportFinishedHandler,
- removeReportFinishedHandler: this.removeReportFinishedHandler,
}}
>
{},
- addReportFinishedHandler: (func: () => void) => {},
- removeReportFinishedHandler: (func: () => void) => {},
})
diff --git a/frontend/src/components/shared/StreamlitMarkdown/StreamlitMarkdown.test.tsx b/frontend/src/components/shared/StreamlitMarkdown/StreamlitMarkdown.test.tsx
index 63fa3f35bf9f..fd05e8ffccb1 100644
--- a/frontend/src/components/shared/StreamlitMarkdown/StreamlitMarkdown.test.tsx
+++ b/frontend/src/components/shared/StreamlitMarkdown/StreamlitMarkdown.test.tsx
@@ -22,7 +22,6 @@ import { mount } from "enzyme"
import {
linkWithTargetBlank,
linkReferenceHasParens,
- createAnchorFromText,
} from "./StreamlitMarkdown"
// Fixture Generator
@@ -34,20 +33,6 @@ const getMarkdownElement = (body: string): ReactElement => {
return
}
-describe("createAnchorFromText", () => {
- it("generates slugs correctly", () => {
- const cases = [
- ["some header", "some-header"],
- ["some -24$35-9824 header", "some-24-35-9824-header"],
- ["blah___blah___blah", "blah-blah-blah"],
- ]
-
- cases.forEach(([s, want]) => {
- expect(createAnchorFromText(s)).toEqual(want)
- })
- })
-})
-
describe("linkReference", () => {
it("renders a link with _blank target", () => {
const body = "Some random URL like [Streamlit](https://streamlit.io/)"
diff --git a/frontend/src/components/shared/StreamlitMarkdown/StreamlitMarkdown.tsx b/frontend/src/components/shared/StreamlitMarkdown/StreamlitMarkdown.tsx
index b08ff48a66b8..b2b8fe4ee2ec 100644
--- a/frontend/src/components/shared/StreamlitMarkdown/StreamlitMarkdown.tsx
+++ b/frontend/src/components/shared/StreamlitMarkdown/StreamlitMarkdown.tsx
@@ -17,23 +17,16 @@
import React, { ReactElement, ReactNode, Fragment, PureComponent } from "react"
import ReactMarkdown from "react-markdown"
-import { once } from "lodash"
// @ts-ignore
import htmlParser from "react-markdown/plugins/html-parser"
// @ts-ignore
import { BlockMath, InlineMath } from "react-katex"
// @ts-ignore
import RemarkMathPlugin from "remark-math"
-import { Link as LinkIcon } from "react-feather"
// @ts-ignore
import RemarkEmoji from "remark-emoji"
-import PageLayoutContext from "components/core/PageLayoutContext"
import CodeBlock from "components/elements/CodeBlock/"
-import {
- StyledStreamlitMarkdown,
- StyledLinkIconContainer,
- StyledLinkIcon,
-} from "./styled-components"
+import { StyledStreamlitMarkdown } from "./styled-components"
import "katex/dist/katex.min.css"
@@ -50,128 +43,6 @@ export interface Props {
allowHTML: boolean
}
-/**
- * Creates a slug suitable for use as an anchor given a string.
- * Splits the string on non-alphanumeric characters, and joins with a dash.
- */
-export function createAnchorFromText(text: string | null): string {
- const newAnchor = text
- ?.toLowerCase()
- .split(/[^A-Za-z0-9]/)
- .filter(Boolean)
- .join("-")
- return newAnchor || ""
-}
-
-// wrapping in `once` ensures we only scroll once
-const scrollNodeIntoView = once((node: HTMLElement): void => {
- node.scrollIntoView(true)
-})
-
-interface HeadingWithAnchorProps {
- tag: string
- anchor?: string
- children: [ReactElement]
-}
-
-interface CustomHeadingProps {
- level: string | number
- children: [ReactElement]
-}
-
-interface CustomParsedHtmlProps {
- type: ReactElement
- element: {
- type: string
- props: {
- "data-anchor": string
- children: [ReactElement]
- }
- }
-}
-
-function HeadingWithAnchor({
- tag,
- anchor: propsAnchor,
- children,
-}: HeadingWithAnchorProps): ReactElement {
- const [elementId, setElementId] = React.useState(propsAnchor)
- const [target, setTarget] = React.useState(null)
-
- const {
- addReportFinishedHandler,
- removeReportFinishedHandler,
- } = React.useContext(PageLayoutContext)
-
- const onReportFinished = React.useCallback(() => {
- if (target !== null) {
- // wait a bit for everything on page to finish loading
- window.setTimeout(() => {
- scrollNodeIntoView(target)
- }, 300)
- }
- }, [target])
-
- React.useEffect(() => {
- addReportFinishedHandler(onReportFinished)
- return () => {
- removeReportFinishedHandler(onReportFinished)
- }
- }, [addReportFinishedHandler, removeReportFinishedHandler, onReportFinished])
-
- const ref = React.useCallback(
- node => {
- if (node === null) {
- return
- }
-
- const anchor = propsAnchor || createAnchorFromText(node.textContent)
- setElementId(anchor)
- if (window.location.hash.slice(1) === anchor) {
- setTarget(node)
- }
- },
- [propsAnchor]
- )
-
- return React.createElement(
- tag,
- { ref, id: elementId },
-
- {elementId && (
-
-
-
- )}
- {children}
-
- )
-}
-
-function CustomHeading({ level, children }: CustomHeadingProps): ReactElement {
- return {children}
-}
-
-function CustomParsedHtml(props: CustomParsedHtmlProps): ReactElement {
- const {
- element: { type, props: elementProps },
- } = props
-
- const headingElements = ["h1", "h2", "h3", "h4", "h5", "h6"]
- if (!headingElements.includes(type)) {
- // casting to any because ReactMarkdown's types are funky
- // but this just means "call the original renderer provided by ReactMarkdown"
- return (ReactMarkdown.renderers.parsedHtml as any)(props)
- }
-
- const { "data-anchor": anchor, children } = elementProps
- return (
-
- {children}
-
- )
-}
-
/**
* Wraps the component to include our standard
* renderers and AST plugins (for syntax highlighting, HTML support, etc).
@@ -200,8 +71,6 @@ class StreamlitMarkdown extends PureComponent {
math: (props: { value: string }): ReactElement => (
{props.value}
),
- heading: CustomHeading,
- parsedHtml: CustomParsedHtml,
}
const plugins = [RemarkMathPlugin, RemarkEmoji]
@@ -236,13 +105,8 @@ interface LinkReferenceProps {
// Using target="_blank" without rel="noopener noreferrer" is a security risk:
// see https://mathiasbynens.github.io/rel-noopener
export function linkWithTargetBlank(props: LinkProps): ReactElement {
- // if it's a #hash link, don't open in new tab
- if (props.href.startsWith("#")) {
- const { children, ...rest } = props
- return {children}
- }
-
const { href, title, children } = props
+
return (
{children}
diff --git a/frontend/src/components/shared/StreamlitMarkdown/styled-components.ts b/frontend/src/components/shared/StreamlitMarkdown/styled-components.ts
index e60b858feb4d..4636326fd8c7 100644
--- a/frontend/src/components/shared/StreamlitMarkdown/styled-components.ts
+++ b/frontend/src/components/shared/StreamlitMarkdown/styled-components.ts
@@ -23,23 +23,3 @@ export const StyledStreamlitMarkdown = styled.div(({ theme }) => ({
border: `1px solid ${theme.colors.lightGray}`,
},
}))
-
-export const StyledLinkIconContainer = styled.div(({ theme }) => ({
- position: "relative",
- left: "-30px",
- paddingLeft: "30px",
- a: {
- display: "none",
- },
- ":hover": {
- a: {
- display: "inline-block",
- },
- },
-}))
-
-export const StyledLinkIcon = styled.a(({ theme }) => ({
- position: "absolute",
- top: "-2px",
- left: 0,
-}))
diff --git a/lib/streamlit/elements/markdown.py b/lib/streamlit/elements/markdown.py
index ec74c1f574fc..6b57b3096ba3 100644
--- a/lib/streamlit/elements/markdown.py
+++ b/lib/streamlit/elements/markdown.py
@@ -78,7 +78,7 @@ def markdown(self, body, unsafe_allow_html=False):
return self.dg._enqueue("markdown", markdown_proto)
- def header(self, body, anchor=None):
+ def header(self, body):
"""Display text in header formatting.
Parameters
@@ -86,10 +86,6 @@ def header(self, body, anchor=None):
body : str
The text to display.
- anchor : str
- The anchor name of the header that can be accessed with #anchor
- in the URL. If omitted, it generates an anchor using the body.
-
Example
-------
>>> st.header('This is a header')
@@ -100,14 +96,10 @@ def header(self, body, anchor=None):
"""
header_proto = MarkdownProto()
- if anchor is None:
- header_proto.body = f"## {clean_text(body)}"
- else:
- header_proto.body = f'{clean_text(body)}
'
- header_proto.allow_html = True
+ header_proto.body = "## %s" % clean_text(body)
return self.dg._enqueue("markdown", header_proto)
- def subheader(self, body, anchor=None):
+ def subheader(self, body):
"""Display text in subheader formatting.
Parameters
@@ -115,10 +107,6 @@ def subheader(self, body, anchor=None):
body : str
The text to display.
- anchor : str
- The anchor name of the header that can be accessed with #anchor
- in the URL. If omitted, it generates an anchor using the body.
-
Example
-------
>>> st.subheader('This is a subheader')
@@ -129,12 +117,7 @@ def subheader(self, body, anchor=None):
"""
subheader_proto = MarkdownProto()
- if anchor is None:
- subheader_proto.body = f"### {clean_text(body)}"
- else:
- subheader_proto.body = f'{clean_text(body)}
'
- subheader_proto.allow_html = True
-
+ subheader_proto.body = "### %s" % clean_text(body)
return self.dg._enqueue("markdown", subheader_proto)
def code(self, body, language="python"):
@@ -170,7 +153,7 @@ def code(self, body, language="python"):
code_proto.body = clean_text(markdown)
return self.dg._enqueue("markdown", code_proto)
- def title(self, body, anchor=None):
+ def title(self, body):
"""Display text in title formatting.
Each document should have a single `st.title()`, although this is not
@@ -181,10 +164,6 @@ def title(self, body, anchor=None):
body : str
The text to display.
- anchor : str
- The anchor name of the header that can be accessed with #anchor
- in the URL. If omitted, it generates an anchor using the body.
-
Example
-------
>>> st.title('This is a title')
@@ -195,11 +174,7 @@ def title(self, body, anchor=None):
"""
title_proto = MarkdownProto()
- if anchor is None:
- title_proto.body = f"# {clean_text(body)}"
- else:
- title_proto.body = f'{clean_text(body)}
'
- title_proto.allow_html = True
+ title_proto.body = "# %s" % clean_text(body)
return self.dg._enqueue("markdown", title_proto)
def latex(self, body):
diff --git a/lib/tests/streamlit/streamlit_test.py b/lib/tests/streamlit/streamlit_test.py
index a708d186d667..fd55b570a0df 100644
--- a/lib/tests/streamlit/streamlit_test.py
+++ b/lib/tests/streamlit/streamlit_test.py
@@ -292,15 +292,6 @@ def test_st_header(self):
el = self.get_delta_from_queue().new_element
self.assertEqual(el.markdown.body, "## some header")
- def test_st_header_with_anchor(self):
- """Test st.header with anchor."""
- st.header("some header", anchor="some-anchor")
-
- el = self.get_delta_from_queue().new_element
- self.assertEqual(
- el.markdown.body, 'some header
'
- )
-
def test_st_help(self):
"""Test st.help."""
st.help(st.header)
@@ -312,7 +303,7 @@ def test_st_help(self):
el.doc_string.doc_string.startswith("Display text in header formatting.")
)
self.assertEqual(el.doc_string.type, "")
- self.assertEqual(el.doc_string.signature, "(body, anchor=None)")
+ self.assertEqual(el.doc_string.signature, "(body)")
def test_st_image_PIL_image(self):
"""Test st.image with PIL image."""
@@ -591,15 +582,6 @@ def test_st_subheader(self):
el = self.get_delta_from_queue().new_element
self.assertEqual(el.markdown.body, "### some subheader")
- def test_st_subheader_with_anchor(self):
- """Test st.subheader with anchor."""
- st.subheader("some subheader", anchor="some-anchor")
-
- el = self.get_delta_from_queue().new_element
- self.assertEqual(
- el.markdown.body, 'some subheader
'
- )
-
def test_st_success(self):
"""Test st.success."""
st.success("some success")
@@ -635,15 +617,6 @@ def test_st_title(self):
el = self.get_delta_from_queue().new_element
self.assertEqual(el.markdown.body, "# some title")
- def test_st_title_with_anchor(self):
- """Test st.title with anchor."""
- st.title("some title", anchor="some-anchor")
-
- el = self.get_delta_from_queue().new_element
- self.assertEqual(
- el.markdown.body, 'some title
'
- )
-
def test_st_vega_lite_chart(self):
"""Test st.vega_lite_chart."""
pass