Skip to content

Commit 2f46ac0

Browse files
authored
Merge pull request #26 from atom-ide-community/float-pane
2 parents e8bb587 + 2ac705b commit 2f46ac0

File tree

5 files changed

+152
-115
lines changed

5 files changed

+152
-115
lines changed

src-commons-ui/float-pane/HTMLView.tsx

Lines changed: 0 additions & 63 deletions
This file was deleted.
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import * as React from "react"
2+
import DOMPurify from "dompurify"
3+
import { MarkdownService } from "../../types-packages/main"
4+
import { getMarkdownRenderer } from "../MarkdownRenderer"
5+
6+
export interface Props {
7+
markdown: Array<string> | string
8+
grammarName?: string
9+
renderer?: MarkdownService
10+
containerClassName: string
11+
contentClassName: string
12+
// already rendered markdown
13+
html?: Array<string> | string
14+
}
15+
16+
interface State {
17+
markdown: string
18+
}
19+
20+
/**
21+
* A react component that can hosts markdown texts
22+
*/
23+
export class MarkdownView extends React.Component<Props, State> {
24+
state: State = { markdown: "" }
25+
26+
render() {
27+
return (
28+
<div className={this.props.containerClassName} onWheel={(e) => this.onMouseWheel(e)}>
29+
<div
30+
className={this.props.contentClassName}
31+
dangerouslySetInnerHTML={{
32+
__html: this.state.markdown,
33+
}}
34+
/>
35+
</div>
36+
)
37+
}
38+
39+
/**
40+
* handles the mouse wheel event to enable scrolling over long text
41+
* @param evt the mouse wheel event being triggered
42+
*/
43+
onMouseWheel(evt: React.WheelEvent) {
44+
evt.stopPropagation()
45+
}
46+
47+
/**
48+
Calls `getDocumentationHtml` to convert Markdown to markdown
49+
*/
50+
async componentDidMount() {
51+
this.setState({
52+
markdown: (await renderMarkdown(this.props.markdown, this.props.grammarName, this.props.renderer)) ?? "",
53+
})
54+
}
55+
}
56+
57+
/**
58+
* convert the markdown documentation to markdown
59+
* @param markdownTexts the documentation text in markdown format to be converted
60+
* @param grammarName the default grammar used for embedded code samples
61+
* @param renderer markdown service to be used for rendering
62+
* @return a promise object to track the asynchronous operation
63+
*/
64+
export async function renderMarkdown(
65+
markdownTexts: Array<string> | string,
66+
grammarName: string = atom.workspace.getActiveTextEditor()?.getGrammar().scopeName?.toLowerCase() || "",
67+
renderer?: MarkdownService
68+
): Promise<string | null> {
69+
if (markdownTexts === undefined) {
70+
return null
71+
}
72+
73+
let markdownText = ""
74+
// if Array
75+
if (Array.isArray(markdownTexts)) {
76+
if (markdownTexts.length === 0) {
77+
return null
78+
}
79+
markdownText = (markdownTexts as Array<string>).join("\r\n")
80+
}
81+
// if string
82+
else {
83+
//@ts-ignore
84+
markdownText = markdownTexts
85+
}
86+
if (renderer) {
87+
return DOMPurify.sanitize(await renderer.render(markdownText, grammarName))
88+
} else {
89+
// Use built-in markdown renderer (it already does sanitization)
90+
const render = await getMarkdownRenderer()
91+
return await render(markdownText, grammarName)
92+
}
93+
}

src-commons-ui/float-pane/ReactView.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as React from "react"
22

3-
interface Props {
3+
export interface Props {
44
component: () => React.ReactElement
55
containerClassName: string
66
contentClassName: string

src-commons-ui/float-pane/SnippetView.tsx

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,46 @@ import DOMPurify from "dompurify"
33
import { MarkdownService } from "../../types-packages/main"
44
import { getMarkdownRenderer } from "../MarkdownRenderer"
55

6-
interface Props {
7-
snippet: string
6+
export interface Props {
7+
snippet: Array<string> | string
8+
grammarName?: string
9+
renderer?: MarkdownService
810
containerClassName: string
911
contentClassName: string
1012
}
1113

12-
interface State {}
14+
interface State {
15+
snippet: string
16+
}
1317

1418
/**
1519
* A React component that hosts a code snippet with syntax highlighting
1620
*/
1721
export class SnippetView extends React.Component<Props, State> {
22+
state = { snippet: "" }
23+
1824
render() {
1925
return (
2026
<div className={this.props.containerClassName}>
2127
<div
2228
className={this.props.contentClassName}
2329
dangerouslySetInnerHTML={{
24-
__html: DOMPurify.sanitize(this.props.snippet),
30+
__html: this.state.snippet,
2531
}}
2632
/>
2733
</div>
2834
)
2935
}
36+
37+
async componentDidMount() {
38+
this.setState({
39+
snippet: (await getSnippetHtml(this.props.snippet, this.props.grammarName, this.props.renderer)) ?? "",
40+
})
41+
}
3042
}
3143

44+
const regExpLSPPrefix = /^\((method|property|parameter|alias)\)\W/
45+
3246
/**
3347
* converts a given code snippet into syntax formatted HTML
3448
* @param snippets the code snippet to be converted
@@ -37,12 +51,24 @@ export class SnippetView extends React.Component<Props, State> {
3751
* @return a promise object to track the asynchronous operation
3852
*/
3953
export async function getSnippetHtml(
40-
snippets: Array<String>,
41-
grammarName: string,
54+
snippets: Array<string> | string,
55+
grammarName: string = atom.workspace.getActiveTextEditor()?.getGrammar().scopeName?.toLowerCase() || "",
4256
renderer?: MarkdownService
4357
): Promise<string | null> {
44-
if (snippets !== undefined && snippets.length > 0) {
45-
const regExpLSPPrefix = /^\((method|property|parameter|alias)\)\W/
58+
if (snippets === undefined) {
59+
return null
60+
}
61+
62+
// if string
63+
if (typeof snippets === "string") {
64+
snippets = [snippets]
65+
}
66+
67+
// if Array
68+
if (Array.isArray(snippets)) {
69+
if (snippets.length === 0) {
70+
return null
71+
}
4672
const divElem = document.createElement("div")
4773
snippets.forEach((snippet) => {
4874
const preElem = document.createElement("pre")
@@ -52,13 +78,15 @@ export async function getSnippetHtml(
5278
preElem.appendChild(codeElem)
5379
divElem.appendChild(preElem)
5480
})
81+
5582
if (renderer) {
56-
return renderer.render(divElem.outerHTML, grammarName)
83+
return DOMPurify.sanitize(await renderer.render(divElem.outerHTML, grammarName))
5784
} else {
58-
// Use built-in markdown renderer when the markdown service is not available
85+
// Use built-in markdown renderer (it already does sanitization)
5986
const render = await getMarkdownRenderer()
6087
return render(divElem.outerHTML, grammarName)
6188
}
89+
} else {
90+
return null
6291
}
63-
return null
6492
}

src-commons-ui/float-pane/ViewContainer.tsx

Lines changed: 19 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
import { HTMLView } from "./HTMLView"
2-
import { SnippetView } from "./SnippetView"
3-
import { ReactView } from "./ReactView"
1+
import { MarkdownView, Props as MarkdownViewProps } from "./MarkdownView"
2+
import { SnippetView, Props as SnippetViewProps } from "./SnippetView"
3+
import { ReactView, Props as ReactViewProps } from "./ReactView"
44
import type { ReactElement } from "react"
55
import * as React from "react"
66
import ReactDOM from "react-dom"
7-
import type { Datatip } from "../../types-packages/main.d"
87

98
export const DATATIP_ACTIONS = Object.freeze({
109
PIN: "PIN",
@@ -17,16 +16,15 @@ const IconsForAction = {
1716
}
1817

1918
interface Props {
20-
component?: { element: () => ReactElement; containerClassName: string; contentClassName: string }
21-
html?: { element: string; containerClassName: string; contentClassName: string }
22-
snippet?: { element: string; containerClassName: string; contentClassName: string }
23-
action: string
24-
actionTitle: string
19+
component?: ReactViewProps
20+
markdown?: MarkdownViewProps
21+
snippet?: SnippetViewProps
22+
action?: string
23+
actionTitle?: string
2524
className?: string
26-
datatip: Datatip
27-
onActionClick: Function
28-
onMouseDown: Function
29-
onClickCapture: Function
25+
onActionClick?: Function
26+
onMouseDown?: Function
27+
onClickCapture?: Function
3028
}
3129

3230
interface State {}
@@ -36,28 +34,18 @@ interface State {}
3634
*/
3735
export class ViewContainer extends React.Component<Props, State> {
3836
actionButton?: JSX.Element
39-
classNames: string
40-
children: Array<JSX.Element>
41-
42-
rootElement: HTMLElement
43-
44-
constructor(props: Props) {
45-
super(props)
46-
this.children = []
47-
this.updateChildren()
48-
this.rootElement = document.createElement("div")
49-
const glowClass = atom.config.get("atom-ide-datatip.glowOnHover") ? "datatip-glow" : ""
50-
this.classNames = `${String(props.className)} datatip-element ${glowClass}`
51-
}
37+
children: Array<JSX.Element> = []
38+
rootElement: HTMLElement = document.createElement("div")
5239

5340
/**
5441
* renders the data tip view component
5542
* @return the data tip view element
5643
*/
5744
render() {
5845
this.actionButton = this.ActionClick(this.props.action, this.props.actionTitle)
46+
this.updateChildren()
5947
return (
60-
<div className={this.classNames} {...this.props.onMouseDown} {...this.props.onClickCapture}>
48+
<div className={`${String(this.props.className)} datatip-element`} {...this.props.onMouseDown} {...this.props.onClickCapture}>
6149
{this.children}
6250
{this.actionButton}
6351
</div>
@@ -74,22 +62,13 @@ export class ViewContainer extends React.Component<Props, State> {
7462
*/
7563
updateChildren() {
7664
if (this.props.component) {
77-
const { element, containerClassName, contentClassName } = this.props.component
78-
this.children.push(
79-
<ReactView component={element} containerClassName={containerClassName} contentClassName={contentClassName} />
80-
)
65+
this.children.push(<ReactView {...this.props.component} />)
8166
}
8267
if (this.props.snippet) {
83-
const { element, containerClassName, contentClassName } = this.props.snippet
84-
this.children.push(
85-
<SnippetView snippet={element} containerClassName={containerClassName} contentClassName={contentClassName} />
86-
)
68+
this.children.push(<SnippetView {...this.props.snippet} />)
8769
}
88-
if (this.props.html) {
89-
const { element, containerClassName, contentClassName } = this.props.html
90-
this.children.push(
91-
<HTMLView html={element} containerClassName={containerClassName} contentClassName={contentClassName} />
92-
)
70+
if (this.props.markdown) {
71+
this.children.push(<MarkdownView {...this.props.markdown} />)
9372
}
9473
}
9574

0 commit comments

Comments
 (0)