Skip to content

Commit

Permalink
feat: node actions (#18248)
Browse files Browse the repository at this point in the history
  • Loading branch information
daibhin authored Nov 3, 2023
1 parent 4a27e55 commit f9c3f9f
Show file tree
Hide file tree
Showing 16 changed files with 232 additions and 163 deletions.
4 changes: 4 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ module.exports = {
element: 'Select',
message: 'use <LemonSelect> instead',
},
{
element: 'LemonButtonWithDropdown',
message: 'use <LemonMenu> with a <LemonButton> child instead',
},
],
},
],
Expand Down
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-button--sizes.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 21 additions & 0 deletions frontend/src/lib/lemon-ui/LemonButton/LemonButton.scss
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,27 @@
padding-right: 0.5rem;
}

&.LemonButton--xsmall {
min-height: 1.5rem;
padding: 0.125rem 0.375rem;
gap: 0.25rem;
font-size: 0.75rem;

.LemonButton__icon {
font-size: 0.875rem;
}

&.LemonButton--has-icon:not(.LemonButton--no-padding),
&.LemonButton--no-content:not(.LemonButton--no-padding) {
padding-left: 0.25rem;
}

&.LemonButton--has-side-icon:not(.LemonButton--no-padding),
&.LemonButton--no-content:not(.LemonButton--no-padding) {
padding-right: 0.25rem;
}
}

&.LemonButton--small {
min-height: 2rem;
padding: 0.125rem 0.5rem;
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/lib/lemon-ui/LemonButton/LemonButton.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export const TextOnly = (): JSX.Element => {
}

export const Sizes = (): JSX.Element => {
const sizes: LemonButtonProps['size'][] = ['small', 'medium', 'large']
const sizes: LemonButtonProps['size'][] = ['xsmall', 'small', 'medium', 'large']

return (
<div className="space-y-2">
Expand All @@ -102,7 +102,7 @@ export const Sizes = (): JSX.Element => {
}

export const SizesIconOnly = (): JSX.Element => {
const sizes: LemonButtonProps['size'][] = ['small', 'medium', 'large']
const sizes: LemonButtonProps['size'][] = ['xsmall', 'small', 'medium', 'large']

return (
<div className="space-y-2">
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/lib/lemon-ui/LemonButton/LemonButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export interface LemonButtonPropsBase
/** Like plain `disabled`, except we enforce a reason to be shown in the tooltip. */
disabledReason?: string | null | false
noPadding?: boolean
size?: 'small' | 'medium' | 'large'
size?: 'xsmall' | 'small' | 'medium' | 'large'
'data-attr'?: string
'aria-label'?: string
}
Expand Down
18 changes: 8 additions & 10 deletions frontend/src/scenes/notebooks/Nodes/NodeWrapper.scss
Original file line number Diff line number Diff line change
Expand Up @@ -39,22 +39,20 @@

.NotebookNode__gap {
display: flex;
gap: 0.25rem;
gap: 0.2rem;
overflow: hidden;
transition: all 150ms linear 1000ms;
opacity: 0;
height: 1rem;
height: 1.25rem;
align-items: center;
}

&--has-actions {
&:hover,
&--selected {
.NotebookNode__gap {
opacity: 1;
height: 2.5rem;
transition: all 150ms linear;
}
&--editable:hover,
&--selected {
.NotebookNode__gap {
opacity: 1;
height: 2rem;
transition: all 150ms linear;
}
}

Expand Down
82 changes: 37 additions & 45 deletions frontend/src/scenes/notebooks/Nodes/NodeWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,17 @@ import {
NodeViewWrapper,
mergeAttributes,
ReactNodeViewRenderer,
ExtendedRegExpMatchArray,
Attribute,
NodeViewProps,
getExtensionField,
} from '@tiptap/react'
import { memo, useCallback, useEffect, useRef } from 'react'
import { memo, useCallback, useEffect, useRef, useState } from 'react'
import clsx from 'clsx'
import {
IconClose,
IconDragHandle,
IconFilter,
IconLink,
IconPlusMini,
IconPlus,
IconUnfoldLess,
IconUnfoldMore,
} from 'lib/lemon-ui/icons'
Expand All @@ -29,31 +27,19 @@ import { NotebookNodeResource } from '~/types'
import { ErrorBoundary } from '~/layout/ErrorBoundary'
import { NotebookNodeContext, NotebookNodeLogicProps, notebookNodeLogic } from './notebookNodeLogic'
import { posthogNodePasteRule, useSyncedAttributes } from './utils'
import { NotebookNodeAttributes, NotebookNodeProps, CustomNotebookNodeAttributes } from '../Notebook/utils'
import {
KNOWN_NODES,
NotebookNodeProps,
CustomNotebookNodeAttributes,
CreatePostHogWidgetNodeOptions,
NodeWrapperProps,
} from '../Notebook/utils'
import { useWhyDidIRender } from 'lib/hooks/useWhyDidIRender'
import { NotebookNodeTitle } from './components/NotebookNodeTitle'
import { notebookNodeLogicType } from './notebookNodeLogicType'
import { SlashCommandsPopover } from '../Notebook/SlashCommands'
import posthog from 'posthog-js'

// TODO: fix the typing of string to NotebookNodeType
const KNOWN_NODES: Record<string, CreatePostHogWidgetNodeOptions<any>> = {}

type NodeWrapperProps<T extends CustomNotebookNodeAttributes> = Omit<NotebookNodeLogicProps, 'notebookLogic'> &
NotebookNodeProps<T> & {
Component: (props: NotebookNodeProps<T>) => JSX.Element | null

// View only props
href?: string | ((attributes: NotebookNodeAttributes<T>) => string | undefined)
expandable?: boolean
selected?: boolean
heightEstimate?: number | string
minHeight?: number | string
/** If true the metadata area will only show when hovered if in editing mode */
autoHideMetadata?: boolean
/** Expand the node if the component is clicked */
expandOnClick?: boolean
}

function NodeWrapper<T extends CustomNotebookNodeAttributes>(props: NodeWrapperProps<T>): JSX.Element {
const {
nodeType,
Expand All @@ -76,6 +62,7 @@ function NodeWrapper<T extends CustomNotebookNodeAttributes>(props: NodeWrapperP
const mountedNotebookLogic = useMountedLogic(notebookLogic)
const { isEditable, editingNodeId, containerSize } = useValues(mountedNotebookLogic)
const { unregisterNodeLogic } = useActions(notebookLogic)
const [slashCommandsPopoverVisible, setSlashCommandsPopoverVisible] = useState<boolean>(false)

const logicProps: NotebookNodeLogicProps = {
...props,
Expand Down Expand Up @@ -130,8 +117,9 @@ function NodeWrapper<T extends CustomNotebookNodeAttributes>(props: NodeWrapperP
const onActionsAreaClick = (): void => {
// Clicking in the area of the actions without selecting a specific action likely indicates the user wants to
// add new content below. If we are in editing mode, we should select the next line if there is one, otherwise
insertOrSelectNextLine()
// setTextSelection(getPos() + 1)
if (!slashCommandsPopoverVisible) {
insertOrSelectNextLine()
}
}

const parsedHref = typeof href === 'function' ? href(attributes) : href
Expand All @@ -147,9 +135,10 @@ function NodeWrapper<T extends CustomNotebookNodeAttributes>(props: NodeWrapperP
<div
ref={ref}
className={clsx(nodeType, 'NotebookNode', {
'NotebookNode--selected': isEditable && selected,
'NotebookNode--auto-hide-metadata': autoHideMetadata,
'NotebookNode--has-actions': getPos && isEditable && actions.length,
'NotebookNode--editable': getPos && isEditable,
'NotebookNode--selected': isEditable && selected,
'NotebookNode--active': slashCommandsPopoverVisible,
})}
>
<div className="NotebookNode__box">
Expand Down Expand Up @@ -239,15 +228,32 @@ function NodeWrapper<T extends CustomNotebookNodeAttributes>(props: NodeWrapperP
// UX improvement so that the actions don't get in the way of the cursor
onClick={() => onActionsAreaClick()}
>
{getPos && isEditable && actions.length ? (
{getPos && isEditable ? (
<>
<SlashCommandsPopover
mode="add"
getPos={() => getPos() + 1}
visible={slashCommandsPopoverVisible}
onClose={() => setSlashCommandsPopoverVisible(false)}
>
<LemonButton
size="xsmall"
type="secondary"
status="primary"
icon={<IconPlus />}
onClick={(e) => {
e.stopPropagation()
setSlashCommandsPopoverVisible(true)
}}
/>
</SlashCommandsPopover>
{actions.map((x, i) => (
<LemonButton
key={i}
size="xsmall"
type="secondary"
status="primary"
size="small"
icon={x.icon ?? <IconPlusMini />}
icon={x.icon ?? <IconPlus />}
onClick={(e) => {
e.stopPropagation()
x.onClick()
Expand All @@ -268,19 +274,6 @@ function NodeWrapper<T extends CustomNotebookNodeAttributes>(props: NodeWrapperP

export const MemoizedNodeWrapper = memo(NodeWrapper) as typeof NodeWrapper

export type CreatePostHogWidgetNodeOptions<T extends CustomNotebookNodeAttributes> = Omit<
NodeWrapperProps<T>,
'updateAttributes'
> & {
Component: (props: NotebookNodeProps<T>) => JSX.Element | null
pasteOptions?: {
find: string
getAttributes: (match: ExtendedRegExpMatchArray) => Promise<T | null | undefined> | T | null | undefined
}
attributes: Record<keyof T, Partial<Attribute>>
serializedText?: (attributes: NotebookNodeAttributes<T>) => string
}

export function createPostHogWidgetNode<T extends CustomNotebookNodeAttributes>(
options: CreatePostHogWidgetNodeOptions<T>
): Node {
Expand Down Expand Up @@ -409,5 +402,4 @@ export const NotebookNodeChildRenderer = ({
selected={false}
/>
)
// return
}
1 change: 0 additions & 1 deletion frontend/src/scenes/notebooks/Nodes/notebookNodeLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,6 @@ export const notebookNodeLogic = kea<notebookNodeLogicType>([
setMessageListeners: (_, { listeners }) => listeners,
},
],

titlePlaceholder: [
props.titlePlaceholder,
{
Expand Down
8 changes: 3 additions & 5 deletions frontend/src/scenes/notebooks/Notebook/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import { BacklinkCommandsExtension } from './BacklinkCommands'
import { NotebookNodeEarlyAccessFeature } from '../Nodes/NotebookNodeEarlyAccessFeature'
import { NotebookNodeSurvey } from '../Nodes/NotebookNodeSurvey'
import { InlineMenu } from './InlineMenu'
import NodeGapInsertionExtension from './Extensions/NodeGapInsertion'
import { notebookLogic } from './notebookLogic'
import { sampleOne } from 'lib/utils'
import { NotebookNodeGroup } from '../Nodes/NotebookNodeGroup'
Expand Down Expand Up @@ -121,7 +120,6 @@ export function Editor(): JSX.Element {
NotebookNodeProperties,
SlashCommandsExtension,
BacklinkCommandsExtension,
NodeGapInsertionExtension,
NotebookNodePersonFeed,
NotebookNodeMap,
],
Expand Down Expand Up @@ -190,9 +188,7 @@ export function Editor(): JSX.Element {
.setTextSelection(coordinates.pos)
.insertContent({
type: NotebookNodeType.Image,
attrs: {
file,
},
attrs: { file },
})
.run()

Expand All @@ -211,12 +207,14 @@ export function Editor(): JSX.Element {
getText: () => textContent(editor.state.doc),
getEndPosition: () => editor.state.doc.content.size,
getSelectedNode: () => editor.state.doc.nodeAt(editor.state.selection.$anchor.pos),
getCurrentPosition: () => editor.state.selection.$anchor.pos,
getAdjacentNodes: (pos: number) => getAdjacentNodes(editor, pos),
setEditable: (editable: boolean) => queueMicrotask(() => editor.setEditable(editable, false)),
setContent: (content: JSONContent) => queueMicrotask(() => editor.commands.setContent(content, false)),
setSelection: (position: number) => editor.commands.setNodeSelection(position),
setTextSelection: (position: number | EditorRange) => editor.commands.setTextSelection(position),
focus: (position?: EditorFocusPosition) => queueMicrotask(() => editor.commands.focus(position)),
chain: () => editor.chain().focus(),
destroy: () => editor.destroy(),
deleteRange: (range: EditorRange) => editor.chain().focus().deleteRange(range),
insertContent: (content: JSONContent) => editor.chain().insertContent(content).focus().run(),
Expand Down

This file was deleted.

Loading

0 comments on commit f9c3f9f

Please sign in to comment.