Skip to content

Commit

Permalink
Merge pull request #26 from atom-ide-community/float-pane
Browse files Browse the repository at this point in the history
  • Loading branch information
aminya authored Oct 9, 2020
2 parents e8bb587 + 2ac705b commit 2f46ac0
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 115 deletions.
63 changes: 0 additions & 63 deletions src-commons-ui/float-pane/HTMLView.tsx

This file was deleted.

93 changes: 93 additions & 0 deletions src-commons-ui/float-pane/MarkdownView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import * as React from "react"
import DOMPurify from "dompurify"
import { MarkdownService } from "../../types-packages/main"
import { getMarkdownRenderer } from "../MarkdownRenderer"

export interface Props {
markdown: Array<string> | string
grammarName?: string
renderer?: MarkdownService
containerClassName: string
contentClassName: string
// already rendered markdown
html?: Array<string> | string
}

interface State {
markdown: string
}

/**
* A react component that can hosts markdown texts
*/
export class MarkdownView extends React.Component<Props, State> {
state: State = { markdown: "" }

render() {
return (
<div className={this.props.containerClassName} onWheel={(e) => this.onMouseWheel(e)}>
<div
className={this.props.contentClassName}
dangerouslySetInnerHTML={{
__html: this.state.markdown,
}}
/>
</div>
)
}

/**
* handles the mouse wheel event to enable scrolling over long text
* @param evt the mouse wheel event being triggered
*/
onMouseWheel(evt: React.WheelEvent) {
evt.stopPropagation()
}

/**
Calls `getDocumentationHtml` to convert Markdown to markdown
*/
async componentDidMount() {
this.setState({
markdown: (await renderMarkdown(this.props.markdown, this.props.grammarName, this.props.renderer)) ?? "",
})
}
}

/**
* convert the markdown documentation to markdown
* @param markdownTexts the documentation text in markdown format to be converted
* @param grammarName the default grammar used for embedded code samples
* @param renderer markdown service to be used for rendering
* @return a promise object to track the asynchronous operation
*/
export async function renderMarkdown(
markdownTexts: Array<string> | string,
grammarName: string = atom.workspace.getActiveTextEditor()?.getGrammar().scopeName?.toLowerCase() || "",
renderer?: MarkdownService
): Promise<string | null> {
if (markdownTexts === undefined) {
return null
}

let markdownText = ""
// if Array
if (Array.isArray(markdownTexts)) {
if (markdownTexts.length === 0) {
return null
}
markdownText = (markdownTexts as Array<string>).join("\r\n")
}
// if string
else {
//@ts-ignore
markdownText = markdownTexts
}
if (renderer) {
return DOMPurify.sanitize(await renderer.render(markdownText, grammarName))
} else {
// Use built-in markdown renderer (it already does sanitization)
const render = await getMarkdownRenderer()
return await render(markdownText, grammarName)
}
}
2 changes: 1 addition & 1 deletion src-commons-ui/float-pane/ReactView.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from "react"

interface Props {
export interface Props {
component: () => React.ReactElement
containerClassName: string
contentClassName: string
Expand Down
50 changes: 39 additions & 11 deletions src-commons-ui/float-pane/SnippetView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,46 @@ import DOMPurify from "dompurify"
import { MarkdownService } from "../../types-packages/main"
import { getMarkdownRenderer } from "../MarkdownRenderer"

interface Props {
snippet: string
export interface Props {
snippet: Array<string> | string
grammarName?: string
renderer?: MarkdownService
containerClassName: string
contentClassName: string
}

interface State {}
interface State {
snippet: string
}

/**
* A React component that hosts a code snippet with syntax highlighting
*/
export class SnippetView extends React.Component<Props, State> {
state = { snippet: "" }

render() {
return (
<div className={this.props.containerClassName}>
<div
className={this.props.contentClassName}
dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(this.props.snippet),
__html: this.state.snippet,
}}
/>
</div>
)
}

async componentDidMount() {
this.setState({
snippet: (await getSnippetHtml(this.props.snippet, this.props.grammarName, this.props.renderer)) ?? "",
})
}
}

const regExpLSPPrefix = /^\((method|property|parameter|alias)\)\W/

/**
* converts a given code snippet into syntax formatted HTML
* @param snippets the code snippet to be converted
Expand All @@ -37,12 +51,24 @@ export class SnippetView extends React.Component<Props, State> {
* @return a promise object to track the asynchronous operation
*/
export async function getSnippetHtml(
snippets: Array<String>,
grammarName: string,
snippets: Array<string> | string,
grammarName: string = atom.workspace.getActiveTextEditor()?.getGrammar().scopeName?.toLowerCase() || "",
renderer?: MarkdownService
): Promise<string | null> {
if (snippets !== undefined && snippets.length > 0) {
const regExpLSPPrefix = /^\((method|property|parameter|alias)\)\W/
if (snippets === undefined) {
return null
}

// if string
if (typeof snippets === "string") {
snippets = [snippets]
}

// if Array
if (Array.isArray(snippets)) {
if (snippets.length === 0) {
return null
}
const divElem = document.createElement("div")
snippets.forEach((snippet) => {
const preElem = document.createElement("pre")
Expand All @@ -52,13 +78,15 @@ export async function getSnippetHtml(
preElem.appendChild(codeElem)
divElem.appendChild(preElem)
})

if (renderer) {
return renderer.render(divElem.outerHTML, grammarName)
return DOMPurify.sanitize(await renderer.render(divElem.outerHTML, grammarName))
} else {
// Use built-in markdown renderer when the markdown service is not available
// Use built-in markdown renderer (it already does sanitization)
const render = await getMarkdownRenderer()
return render(divElem.outerHTML, grammarName)
}
} else {
return null
}
return null
}
59 changes: 19 additions & 40 deletions src-commons-ui/float-pane/ViewContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { HTMLView } from "./HTMLView"
import { SnippetView } from "./SnippetView"
import { ReactView } from "./ReactView"
import { MarkdownView, Props as MarkdownViewProps } from "./MarkdownView"
import { SnippetView, Props as SnippetViewProps } from "./SnippetView"
import { ReactView, Props as ReactViewProps } from "./ReactView"
import type { ReactElement } from "react"
import * as React from "react"
import ReactDOM from "react-dom"
import type { Datatip } from "../../types-packages/main.d"

export const DATATIP_ACTIONS = Object.freeze({
PIN: "PIN",
Expand All @@ -17,16 +16,15 @@ const IconsForAction = {
}

interface Props {
component?: { element: () => ReactElement; containerClassName: string; contentClassName: string }
html?: { element: string; containerClassName: string; contentClassName: string }
snippet?: { element: string; containerClassName: string; contentClassName: string }
action: string
actionTitle: string
component?: ReactViewProps
markdown?: MarkdownViewProps
snippet?: SnippetViewProps
action?: string
actionTitle?: string
className?: string
datatip: Datatip
onActionClick: Function
onMouseDown: Function
onClickCapture: Function
onActionClick?: Function
onMouseDown?: Function
onClickCapture?: Function
}

interface State {}
Expand All @@ -36,28 +34,18 @@ interface State {}
*/
export class ViewContainer extends React.Component<Props, State> {
actionButton?: JSX.Element
classNames: string
children: Array<JSX.Element>

rootElement: HTMLElement

constructor(props: Props) {
super(props)
this.children = []
this.updateChildren()
this.rootElement = document.createElement("div")
const glowClass = atom.config.get("atom-ide-datatip.glowOnHover") ? "datatip-glow" : ""
this.classNames = `${String(props.className)} datatip-element ${glowClass}`
}
children: Array<JSX.Element> = []
rootElement: HTMLElement = document.createElement("div")

/**
* renders the data tip view component
* @return the data tip view element
*/
render() {
this.actionButton = this.ActionClick(this.props.action, this.props.actionTitle)
this.updateChildren()
return (
<div className={this.classNames} {...this.props.onMouseDown} {...this.props.onClickCapture}>
<div className={`${String(this.props.className)} datatip-element`} {...this.props.onMouseDown} {...this.props.onClickCapture}>
{this.children}
{this.actionButton}
</div>
Expand All @@ -74,22 +62,13 @@ export class ViewContainer extends React.Component<Props, State> {
*/
updateChildren() {
if (this.props.component) {
const { element, containerClassName, contentClassName } = this.props.component
this.children.push(
<ReactView component={element} containerClassName={containerClassName} contentClassName={contentClassName} />
)
this.children.push(<ReactView {...this.props.component} />)
}
if (this.props.snippet) {
const { element, containerClassName, contentClassName } = this.props.snippet
this.children.push(
<SnippetView snippet={element} containerClassName={containerClassName} contentClassName={contentClassName} />
)
this.children.push(<SnippetView {...this.props.snippet} />)
}
if (this.props.html) {
const { element, containerClassName, contentClassName } = this.props.html
this.children.push(
<HTMLView html={element} containerClassName={containerClassName} contentClassName={contentClassName} />
)
if (this.props.markdown) {
this.children.push(<MarkdownView {...this.props.markdown} />)
}
}

Expand Down

0 comments on commit 2f46ac0

Please sign in to comment.