Skip to content

Commit

Permalink
feat: Allow Markdown in item descriptions (#17241)
Browse files Browse the repository at this point in the history
* feat: Allow Markdown in item descriptions

* Allow forcing Link "open in new" icon for non-text children

* Fix `LemonFileInput` empty child

* Add `LemonMarkdown`

* Simplify `LemonMarkdown` imports

* Update .eslintrc.js

* Update UI snapshots for `chromium` (1)

* Update UI snapshots for `webkit` (2)

* Update UI snapshots for `chromium` (2)

* Increase test timeout

* Update UI snapshots for `chromium` (1)

* Update UI snapshots for `webkit` (2)

* Update UI snapshots for `chromium` (1)

---------

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Paul D'Ambra <[email protected]>
  • Loading branch information
3 people authored Sep 18, 2023
1 parent a352a5b commit 2edc058
Show file tree
Hide file tree
Showing 157 changed files with 243 additions and 85 deletions.
8 changes: 6 additions & 2 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,10 @@ module.exports = {
element: 'a',
message: 'use <Link> instead',
},
{
element: 'ReactMarkdown',
message: 'use <LemonMarkdown> instead',
},
],
},
],
Expand Down Expand Up @@ -175,9 +179,9 @@ module.exports = {
message: 'use <LemonCollapse> instead',
},
{
element:'MonacoEditor',
element: 'MonacoEditor',
message: 'use <CodeEditor> instead',
}
},
],
},
],
Expand Down
2 changes: 1 addition & 1 deletion .storybook/test-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ module.exports = {
const storyContext = (await getStoryContext(page, context)) as StoryContext
const { skip = false, snapshotBrowsers = ['chromium'] } = storyContext.parameters?.testOptions ?? {}

browserContext.setDefaultTimeout(3000) // Reduce the default timeout from 30 s to 3 s to pre-empt Jest timeouts
browserContext.setDefaultTimeout(5000) // Reduce the default timeout from 30 s to 5 s to pre-empt Jest timeouts
if (!skip) {
const currentBrowser = browserContext.browser()!.browserType().name() as SupportedBrowserName
if (snapshotBrowsers.includes(currentBrowser)) {
Expand Down
Binary file modified frontend/__snapshots__/components-cards-text-card--template.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/__snapshots__/exporter-exporter--dashboard.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/__snapshots__/filters-taxonomic-filter--actions.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/__snapshots__/filters-taxonomic-filter--events-free.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/__snapshots__/filters-taxonomic-filter--properties.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/__snapshots__/lemon-ui-lemon-file-input--default.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/__snapshots__/scenes-app-dashboards--edit.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/__snapshots__/scenes-app-dashboards--show.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/__snapshots__/scenes-app-insights--lifecycle-edit.png
Binary file modified frontend/__snapshots__/scenes-app-insights--lifecycle.png
Binary file modified frontend/__snapshots__/scenes-app-insights--retention-edit.png
Binary file modified frontend/__snapshots__/scenes-app-insights--retention.png
Binary file modified frontend/__snapshots__/scenes-app-insights--stickiness-edit.png
Binary file modified frontend/__snapshots__/scenes-app-insights--stickiness.png
Binary file modified frontend/__snapshots__/scenes-app-insights--trends-area.png
Binary file modified frontend/__snapshots__/scenes-app-insights--trends-bar-edit.png
Binary file modified frontend/__snapshots__/scenes-app-insights--trends-bar.png
Binary file modified frontend/__snapshots__/scenes-app-insights--trends-line.png
Binary file modified frontend/__snapshots__/scenes-app-insights--trends-number.png
Binary file modified frontend/__snapshots__/scenes-app-insights--trends-pie-edit.png
Binary file modified frontend/__snapshots__/scenes-app-insights--trends-pie.png
Binary file modified frontend/__snapshots__/scenes-app-insights--trends-table.png
Binary file modified frontend/__snapshots__/scenes-app-insights--trends-value.png
Binary file modified frontend/__snapshots__/scenes-app-insights--trends-world-map.png
Binary file modified frontend/__snapshots__/scenes-app-insights--user-paths-edit.png
Binary file modified frontend/__snapshots__/scenes-app-insights--user-paths.png
Binary file modified frontend/__snapshots__/scenes-app-notebooks--headings.png
Binary file modified frontend/__snapshots__/scenes-app-notebooks--text-formats.png
4 changes: 2 additions & 2 deletions frontend/src/layout/navigation/TopBar/Announcement.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { NewFeatureBanner } from 'lib/introductions/NewFeatureBanner'
import { preflightLogic } from 'scenes/PreflightCheck/preflightLogic'
import { LemonButton, Link } from '@posthog/lemon-ui'
import { IconClose } from 'lib/lemon-ui/icons'
import ReactMarkdown from 'react-markdown'
import { LemonMarkdown } from 'lib/lemon-ui/LemonMarkdown'

window.process = MOCK_NODE_PROCESS

Expand Down Expand Up @@ -37,7 +37,7 @@ export function Announcement(): JSX.Element | null {
</div>
)
} else if (shownAnnouncementType === AnnouncementType.CloudFlag && cloudAnnouncement) {
message = <ReactMarkdown className="strong">{cloudAnnouncement}</ReactMarkdown>
message = <LemonMarkdown className="strong">{cloudAnnouncement}</LemonMarkdown>
} else if (shownAnnouncementType === AnnouncementType.NewFeature) {
message = <NewFeatureBanner />
}
Expand Down
8 changes: 2 additions & 6 deletions frontend/src/layout/navigation/TopBar/notificationsLogic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { ActivityLogItem, humanize, HumanizedActivityLogItem } from 'lib/compone
import type { notificationsLogicType } from './notificationsLogicType'
import { describerFor } from 'lib/components/ActivityLog/activityLogLogic'
import { dayjs } from 'lib/dayjs'
import ReactMarkdown from 'react-markdown'
import posthog from 'posthog-js'
import { LemonMarkdown } from 'lib/lemon-ui/LemonMarkdown'

const POLL_TIMEOUT = 5 * 60 * 1000
const MARK_READ_TIMEOUT = 2500
Expand Down Expand Up @@ -156,11 +156,7 @@ export const notificationsLogic = kea<notificationsLogicType>([
email: '[email protected]',
name: 'Joe',
isSystem: true,
description: (
<>
<ReactMarkdown linkTarget="_blank">{changelogNotification.markdown}</ReactMarkdown>
</>
),
description: <LemonMarkdown>{changelogNotification.markdown}</LemonMarkdown>,
created_at: changelogNotification.notificationDate,
unread: changeLogIsUnread,
}
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/lib/components/Cards/TextCard/TextCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import { LemonButton, LemonButtonWithDropdown, LemonDivider } from '@posthog/lem
import { useActions, useValues } from 'kea'
import { router } from 'kea-router'
import { urls } from 'scenes/urls'
import ReactMarkdown from 'react-markdown'
import { dashboardsModel } from '~/models/dashboardsModel'
import React, { useState } from 'react'
import { CardMeta, Resizeable } from 'lib/components/Cards/CardMeta'
import { LemonMarkdown } from 'lib/lemon-ui/LemonMarkdown'

interface TextCardProps extends React.HTMLAttributes<HTMLDivElement>, Resizeable {
dashboardId?: string | number
Expand All @@ -33,7 +33,7 @@ export function TextContent({ text, closeDetails, style, className }: TextCardBo
return (
// eslint-disable-next-line react/forbid-dom-props
<div className={clsx('p-2 w-full overflow-auto', className)} onClick={() => closeDetails?.()} style={style}>
<ReactMarkdown>{text}</ReactMarkdown>
<LemonMarkdown>{text}</LemonMarkdown>
</div>
)
}
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/lib/components/Cards/TextCard/TextCardModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useActions, useValues } from 'kea'
import { LemonModal } from 'lib/lemon-ui/LemonModal'
import { LemonButton } from 'lib/lemon-ui/LemonButton'
import { Field, Form } from 'kea-forms'
import { LemonTextMarkdown } from 'lib/lemon-ui/LemonTextArea/LemonTextArea'
import { LemonTextAreaMarkdown } from 'lib/lemon-ui/LemonTextArea/LemonTextArea'
import { PayGateMini } from 'lib/components/PayGateMini/PayGateMini'
import { userLogic } from 'scenes/userLogic'

Expand Down Expand Up @@ -71,7 +71,7 @@ export function TextCardModal({
>
<PayGateMini feature={AvailableFeature.DASHBOARD_COLLABORATION}>
<Field name="body" label="">
<LemonTextMarkdown data-attr={'text-card-edit-area'} />
<LemonTextAreaMarkdown data-attr={'text-card-edit-area'} />
</Field>
</PayGateMini>
</Form>
Expand Down
20 changes: 10 additions & 10 deletions frontend/src/lib/components/CodeSnippet/CodeSnippet.scss
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
.CodeSnippet {
position: relative;
font-size: 0.875rem;
&.CodeSnippet--compact {
font-size: 0.8125rem;
.CodeSnippet__actions {
top: 0.375rem;
right: 0.375rem;
}
}
.CodeSnippet__actions {
position: absolute;
display: flex;
top: 0.25rem;
right: 0.25rem;
gap: 0.5rem;
font-size: 1.5rem;

// NOTE: This is not ideal as we should not override core components styles but...
.LemonButton {
background: transparent !important;
box-shadow: transparent !important;

.LemonIcon {
color: #fff;
}
.LemonButton .LemonIcon {
color: #fff;
}
}
}
Expand Down
15 changes: 12 additions & 3 deletions frontend/src/lib/components/CodeSnippet/CodeSnippet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { useValues } from 'kea'
import { featureFlagLogic } from 'lib/logic/featureFlagLogic'
import { FEATURE_FLAGS } from 'lib/constants'
import { useState } from 'react'
import clsx from 'clsx'

export enum Language {
Text = 'text',
Expand Down Expand Up @@ -83,6 +84,7 @@ export interface CodeSnippetProps {
children: string
language?: Language
wrap?: boolean
compact?: boolean
actions?: Action[]
style?: React.CSSProperties
/** What is being copied. @example 'link' */
Expand All @@ -95,6 +97,7 @@ export function CodeSnippet({
children: text,
language = Language.Text,
wrap = false,
compact = false,
style,
actions,
thing = 'snippet',
Expand All @@ -109,15 +112,20 @@ export function CodeSnippet({

return (
// eslint-disable-next-line react/forbid-dom-props
<div className="CodeSnippet" style={style}>
<div className={clsx('CodeSnippet', compact && 'CodeSnippet--compact')} style={style}>
<div className="CodeSnippet__actions">
{actions &&
actions.map(({ icon, callback, popconfirmProps, title }, index) =>
!popconfirmProps ? (
<LemonButton key={`snippet-action-${index}`} onClick={callback} title={title} />
<LemonButton
key={`snippet-action-${index}`}
onClick={callback}
title={title}
size={compact ? 'small' : 'medium'}
/>
) : (
<Popconfirm key={`snippet-action-${index}`} {...popconfirmProps} onConfirm={callback}>
<LemonButton icon={icon} title={title} />
<LemonButton icon={icon} title={title} size={compact ? 'small' : 'medium'} />
</Popconfirm>
)
)}
Expand All @@ -127,6 +135,7 @@ export function CodeSnippet({
onClick={async () => {
text && (await copyToClipboard(text, thing))
}}
size={compact ? 'small' : 'medium'}
/>
</div>
<SyntaxHighlighter
Expand Down
17 changes: 9 additions & 8 deletions frontend/src/lib/components/EditableField/EditableField.scss
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
color: var(--muted);
margin-left: 0.5rem;
}
.EditableField--highlight {
.EditableField__highlight {
display: flex;
flex-direction: row;
align-items: center;
Expand All @@ -25,7 +25,7 @@
white-space: pre-wrap;
overflow: auto;
}
&--editing .EditableField--highlight {
&--editing .EditableField__highlight {
flex-grow: 1;
align-items: flex-end;
width: auto;
Expand All @@ -46,6 +46,13 @@
overflow: scroll;
white-space: pre;
}
.EditableField__actions {
flex-shrink: 0;
display: flex;
align-items: center;
gap: 0.5rem;
margin-left: 0.5rem;
}
input,
textarea {
max-width: 100%;
Expand All @@ -60,10 +67,4 @@
align-self: center;
width: 100%;
}
button {
&:first-of-type {
margin-left: 0.75rem;
}
margin-left: 0.5rem;
}
}
32 changes: 16 additions & 16 deletions frontend/src/lib/components/EditableField/EditableField.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
import { Meta } from '@storybook/react'
import { Meta, StoryFn } from '@storybook/react'

import { EditableField as EditableFieldComponent } from './EditableField'
import { PageHeader } from '../PageHeader'
import { useState } from 'react'

const meta: Meta<typeof EditableFieldComponent> = {
title: 'Components/Editable Field',
component: EditableFieldComponent,
tags: ['autodocs'],
}
export default meta

export function EditableField_(): JSX.Element {
const [savedTitle, setSavedTitle] = useState('Foo')
const [savedDescription, setSavedDescription] = useState('Lorem ipsum dolor sit amet.')
const Template: StoryFn<typeof EditableFieldComponent> = (args) => {
const [value, setValue] = useState(args.value ?? 'Lorem ipsum')

return (
<PageHeader
title={<EditableFieldComponent name="title" value={savedTitle} onSave={(value) => setSavedTitle(value)} />}
caption={
<EditableFieldComponent
name="description"
value={savedDescription}
onSave={(value) => setSavedDescription(value)}
multiline
/>
}
/>
<div className="flex">
<EditableFieldComponent {...args} value={value} onSave={(value) => setValue(value)} />
</div>
)
}

export const Default = Template.bind({})

export const MultilineWithMarkdown = Template.bind({})
MultilineWithMarkdown.args = {
multiline: true,
markdown: true,
value: 'Lorem ipsum **dolor** sit amet, consectetur adipiscing _elit_.',
}
45 changes: 30 additions & 15 deletions frontend/src/lib/components/EditableField/EditableField.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
import './EditableField.scss'
import { IconEdit } from 'lib/lemon-ui/icons'
import { IconEdit, IconMarkdown } from 'lib/lemon-ui/icons'
import { LemonButton } from 'lib/lemon-ui/LemonButton'
import TextareaAutosize from 'react-textarea-autosize'
import clsx from 'clsx'
import { pluralize } from 'lib/utils'
import { Tooltip } from 'lib/lemon-ui/Tooltip'
import { LemonMarkdown } from 'lib/lemon-ui/LemonMarkdown'

interface EditableFieldProps {
export interface EditableFieldProps {
/** What this field stands for. */
name: string
value: string
Expand All @@ -19,6 +20,8 @@ interface EditableFieldProps {
maxLength?: number
autoFocus?: boolean
multiline?: boolean
/** Whether to render the content as Markdown in view mode. */
markdown?: boolean
compactButtons?: boolean
/** Whether this field should be gated behind a "paywall". */
paywall?: boolean
Expand Down Expand Up @@ -46,6 +49,7 @@ export function EditableField({
maxLength,
autoFocus = true,
multiline = false,
markdown = false,
compactButtons = false,
paywall = false,
mode,
Expand Down Expand Up @@ -116,7 +120,7 @@ export function EditableField({
: undefined
}
>
<div className="EditableField--highlight">
<div className="EditableField__highlight">
{isEditing ? (
<>
{multiline ? (
Expand Down Expand Up @@ -151,7 +155,12 @@ export function EditableField({
/>
)}
{!mode && (
<>
<div className="EditableField__actions">
{markdown && (
<Tooltip title="Markdown formatting support">
<IconMarkdown className="text-muted text-2xl" />
</Tooltip>
)}
<LemonButton
title="Cancel editing"
size="small"
Expand All @@ -178,22 +187,28 @@ export function EditableField({
>
{saveButtonText}
</LemonButton>
</>
</div>
)}
</>
) : (
<>
{tentativeValue || <i>{placeholder}</i>}
{tentativeValue && markdown ? (
<LemonMarkdown lowKeyHeadings>{tentativeValue}</LemonMarkdown>
) : (
tentativeValue || <i>{placeholder}</i>
)}
{!mode && (
<LemonButton
title="Edit"
icon={<IconEdit />}
size={compactButtons ? 'small' : undefined}
onClick={() => setLocalIsEditing(true)}
data-attr={`edit-prop-${name}`}
disabled={paywall}
noPadding
/>
<div className="EditableField__actions">
<LemonButton
title="Edit"
icon={<IconEdit />}
size={compactButtons ? 'small' : undefined}
onClick={() => setLocalIsEditing(true)}
data-attr={`edit-prop-${name}`}
disabled={paywall}
noPadding
/>
</div>
)}
</>
)}
Expand Down
Loading

0 comments on commit 2edc058

Please sign in to comment.