refactor: inner blocks note links (#1973)

This commit is contained in:
Mo
2022-11-09 06:06:46 -06:00
committed by GitHub
parent 2438443e53
commit 6bcba01dab
121 changed files with 1641 additions and 130 deletions

View File

@@ -8,6 +8,11 @@ import { FileNode } from './Plugins/EncryptedFilePlugin/Nodes/FileNode'
import FilePlugin from './Plugins/EncryptedFilePlugin/FilePlugin'
import BlockPickerMenuPlugin from './Plugins/BlockPickerPlugin/BlockPickerPlugin'
import { ErrorBoundary } from '@/Utils/ErrorBoundary'
import { LinkingController } from '@/Controllers/LinkingController'
import LinkingControllerProvider from './Contexts/LinkingControllerProvider'
import { BubbleNode } from './Plugins/ItemBubblePlugin/Nodes/BubbleNode'
import ItemBubblePlugin from './Plugins/ItemBubblePlugin/ItemBubblePlugin'
import { NodeObserverPlugin } from './Plugins/NodeObserverPlugin/NodeObserverPlugin'
const StringEllipses = '...'
const NotePreviewCharLimit = 160
@@ -15,9 +20,10 @@ const NotePreviewCharLimit = 160
type Props = {
application: WebApplication
note: SNNote
linkingController: LinkingController
}
export const BlockEditor: FunctionComponent<Props> = ({ note, application }) => {
export const BlockEditor: FunctionComponent<Props> = ({ note, application, linkingController }) => {
const controller = useRef(new BlockEditorController(note, application))
const handleChange = useCallback(
@@ -31,19 +37,34 @@ export const BlockEditor: FunctionComponent<Props> = ({ note, application }) =>
[controller],
)
const handleBubbleRemove = useCallback(
(itemUuid: string) => {
const item = application.items.findItem(itemUuid)
if (item) {
linkingController.unlinkItemFromSelectedItem(item).catch(console.error)
}
},
[linkingController, application],
)
return (
<div className="relative h-full w-full p-5">
<ErrorBoundary>
<BlocksEditorComposer initialValue={note.text} nodes={[FileNode]}>
<BlocksEditor
onChange={handleChange}
className="relative relative resize-none text-base focus:shadow-none focus:outline-none"
>
<ItemSelectionPlugin currentNote={note} />
<FilePlugin />
<BlockPickerMenuPlugin />
</BlocksEditor>
</BlocksEditorComposer>
<LinkingControllerProvider controller={linkingController}>
<BlocksEditorComposer initialValue={note.text} nodes={[FileNode, BubbleNode]}>
<BlocksEditor
onChange={handleChange}
className="relative relative resize-none text-base focus:shadow-none focus:outline-none"
>
<ItemSelectionPlugin currentNote={note} />
<FilePlugin />
<ItemBubblePlugin />
<BlockPickerMenuPlugin />
<NodeObserverPlugin nodeType={BubbleNode} onRemove={handleBubbleRemove} />
<NodeObserverPlugin nodeType={FileNode} onRemove={handleBubbleRemove} />
</BlocksEditor>
</BlocksEditorComposer>
</LinkingControllerProvider>
</ErrorBoundary>
</div>
)

View File

@@ -0,0 +1,36 @@
import { ReactNode, createContext, useContext, memo } from 'react'
import { observer } from 'mobx-react-lite'
import { LinkingController } from '@/Controllers/LinkingController'
const LinkingControllerContext = createContext<LinkingController | undefined>(undefined)
export const useLinkingController = () => {
const value = useContext(LinkingControllerContext)
if (!value) {
throw new Error('Component must be a child of <LinkingControllerProvider />')
}
return value
}
type ChildrenProps = {
children: ReactNode
}
type ProviderProps = {
controller: LinkingController
} & ChildrenProps
const MemoizedChildren = memo(({ children }: ChildrenProps) => <>{children}</>)
const LinkingControllerProvider = ({ controller, children }: ProviderProps) => {
return (
<LinkingControllerContext.Provider value={controller}>
<MemoizedChildren children={children} />
</LinkingControllerContext.Provider>
)
}
export default observer(LinkingControllerProvider)

View File

@@ -1,4 +1,4 @@
import { PopoverItemClassNames, PopoverItemSelectedClassNames } from '@standardnotes/blocks-editor'
import { PopoverItemClassNames, PopoverItemSelectedClassNames } from '../ClassNames'
import { BlockPickerOption } from './BlockPickerOption'
export function BlockPickerMenuItem({

View File

@@ -2,7 +2,6 @@ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext
import { LexicalTypeaheadMenuPlugin, useBasicTypeaheadTriggerMatch } from '@lexical/react/LexicalTypeaheadMenuPlugin'
import { TextNode } from 'lexical'
import { useCallback, useMemo, useState } from 'react'
import { PopoverClassNames } from '@standardnotes/blocks-editor'
import useModal from '@standardnotes/blocks-editor/src/Lexical/Hooks/useModal'
import { InsertTableDialog } from '@standardnotes/blocks-editor/src/Lexical/Plugins/TablePlugin'
import { BlockPickerOption } from './BlockPickerOption'
@@ -20,6 +19,7 @@ import { GetCodeBlock } from './Blocks/Code'
import { GetEmbedsBlocks } from './Blocks/Embeds'
import { GetDynamicTableBlocks, GetTableBlock } from './Blocks/Table'
import Popover from '@/Components/Popover/Popover'
import { PopoverClassNames } from '../ClassNames'
export default function BlockPickerMenuPlugin(): JSX.Element {
const [editor] = useLexicalComposerContext()
@@ -109,7 +109,6 @@ export default function BlockPickerMenuPlugin(): JSX.Element {
x: anchorElementRef.current.offsetLeft,
y: anchorElementRef.current.offsetTop + anchorElementRef.current.offsetHeight,
}}
className={'min-h-80 h-80'}
open={popoverOpen}
togglePopover={() => {
setPopoverOpen((prevValue) => !prevValue)

View File

@@ -0,0 +1,13 @@
import { classNames } from '@/Utils/ConcatenateClassNames'
export const PopoverClassNames = classNames(
'z-dropdown-menu w-full min-w-80',
'cursor-auto flex-col overflow-y-auto rounded bg-default md:h-auto md:max-w-xs h-auto overflow-y-scroll',
)
export const PopoverItemClassNames = classNames(
'flex w-full items-center text-base gap-4 overflow-hidden py-2 px-3 hover:bg-contrast hover:text-foreground',
'focus:bg-info-backdrop cursor-pointer m-0 focus:bg-contrast focus:text-foreground',
)
export const PopoverItemSelectedClassNames = classNames('bg-contrast text-foreground')

View File

@@ -0,0 +1,4 @@
import { createCommand, LexicalCommand } from 'lexical'
export const INSERT_FILE_COMMAND: LexicalCommand<string> = createCommand('INSERT_FILE_COMMAND')
export const INSERT_BUBBLE_COMMAND: LexicalCommand<string> = createCommand('INSERT_BUBBLE_COMMAND')

View File

@@ -1,10 +1,11 @@
import { INSERT_FILE_COMMAND } from './../Commands'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { $insertNodeToNearestRoot } from '@lexical/utils'
import { COMMAND_PRIORITY_EDITOR } from 'lexical'
import { useEffect } from 'react'
import { FileNode } from './Nodes/FileNode'
import { $createParagraphNode, $insertNodes, $isRootOrShadowRoot, COMMAND_PRIORITY_EDITOR } from 'lexical'
import { $createFileNode } from './Nodes/FileUtils'
import { INSERT_FILE_COMMAND } from '@standardnotes/blocks-editor'
import { $wrapNodeInElement } from '@lexical/utils'
export default function FilePlugin(): JSX.Element | null {
const [editor] = useLexicalComposerContext()
@@ -18,7 +19,11 @@ export default function FilePlugin(): JSX.Element | null {
INSERT_FILE_COMMAND,
(payload) => {
const fileNode = $createFileNode(payload)
$insertNodeToNearestRoot(fileNode)
// $insertNodeToNearestRoot(fileNode)
$insertNodes([fileNode])
if ($isRootOrShadowRoot(fileNode.getParentOrThrow())) {
$wrapNodeInElement(fileNode, $createParagraphNode).selectEnd()
}
return true
},

View File

@@ -3,8 +3,9 @@ import { DecoratorBlockNode } from '@lexical/react/LexicalDecoratorBlockNode'
import { $createFileNode, convertToFileElement } from './FileUtils'
import { FileComponent } from './FileComponent'
import { SerializedFileNode } from './SerializedFileNode'
import { ItemNodeInterface } from '../../ItemNodeInterface'
export class FileNode extends DecoratorBlockNode {
export class FileNode extends DecoratorBlockNode implements ItemNodeInterface {
__id: string
static getType(): string {
@@ -45,7 +46,7 @@ export class FileNode extends DecoratorBlockNode {
}
exportDOM(): DOMExportOutput {
const element = document.createElement('div')
const element = document.createElement('span')
element.setAttribute('data-lexical-file-uuid', this.__id)
const text = document.createTextNode(this.getTextContent())
element.append(text)
@@ -74,8 +75,4 @@ export class FileNode extends DecoratorBlockNode {
return <FileComponent className={className} format={this.__format} nodeKey={this.getKey()} fileUuid={this.__id} />
}
isInline(): false {
return false
}
}

View File

@@ -0,0 +1,33 @@
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { $wrapNodeInElement } from '@lexical/utils'
import { COMMAND_PRIORITY_EDITOR, $createParagraphNode, $insertNodes, $isRootOrShadowRoot } from 'lexical'
import { useEffect } from 'react'
import { INSERT_BUBBLE_COMMAND } from '../Commands'
import { BubbleNode } from './Nodes/BubbleNode'
import { $createBubbleNode } from './Nodes/BubbleUtils'
export default function ItemBubblePlugin(): JSX.Element | null {
const [editor] = useLexicalComposerContext()
useEffect(() => {
if (!editor.hasNodes([BubbleNode])) {
throw new Error('ItemBubblePlugin: BubbleNode not registered on editor')
}
return editor.registerCommand<string>(
INSERT_BUBBLE_COMMAND,
(payload) => {
const bubbleNode = $createBubbleNode(payload)
$insertNodes([bubbleNode])
if ($isRootOrShadowRoot(bubbleNode.getParentOrThrow())) {
$wrapNodeInElement(bubbleNode, $createParagraphNode).selectEnd()
}
return true
},
COMMAND_PRIORITY_EDITOR,
)
}, [editor])
return null
}

View File

@@ -0,0 +1,60 @@
import { useCallback, useMemo } from 'react'
import { useApplication } from '@/Components/ApplicationView/ApplicationProvider'
import LinkedItemBubble from '@/Components/LinkedItems/LinkedItemBubble'
import { createLinkFromItem } from '@/Utils/Items/Search/createLinkFromItem'
import { useLinkingController } from '@/Components/BlockEditor/Contexts/LinkingControllerProvider'
import { LinkableItem } from '@/Utils/Items/Search/LinkableItem'
import { useResponsiveAppPane } from '@/Components/ResponsivePane/ResponsivePaneProvider'
import { LexicalNode } from 'lexical'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
export type BubbleComponentProps = Readonly<{
itemUuid: string
node: LexicalNode
}>
export function BubbleComponent({ itemUuid, node }: BubbleComponentProps) {
const application = useApplication()
const [editor] = useLexicalComposerContext()
const linkingController = useLinkingController()
const item = useMemo(() => application.items.findItem(itemUuid), [application, itemUuid])
const { toggleAppPane } = useResponsiveAppPane()
const activateItemAndTogglePane = useCallback(
async (item: LinkableItem) => {
const paneId = await linkingController.activateItem(item)
if (paneId) {
toggleAppPane(paneId)
}
},
[toggleAppPane, linkingController],
)
const unlinkPressed = useCallback(
async (itemToUnlink: LinkableItem) => {
linkingController.unlinkItemFromSelectedItem(itemToUnlink).catch(console.error)
editor.update(() => {
node.remove()
})
},
[linkingController, node, editor],
)
if (!item) {
return <div>Unable to find item {itemUuid}</div>
}
const link = createLinkFromItem(item, 'linked')
return (
<LinkedItemBubble
className="m-1"
link={link}
key={link.id}
activateItem={activateItemAndTogglePane}
unlinkItem={unlinkPressed}
isBidirectional={false}
inlineFlex={true}
/>
)
}

View File

@@ -0,0 +1,76 @@
import { DOMConversionMap, DOMExportOutput, ElementFormatType, LexicalEditor, NodeKey } from 'lexical'
import { DecoratorBlockNode } from '@lexical/react/LexicalDecoratorBlockNode'
import { $createBubbleNode, convertToBubbleElement } from './BubbleUtils'
import { BubbleComponent } from './BubbleComponent'
import { SerializedBubbleNode } from './SerializedBubbleNode'
import { ItemNodeInterface } from '../../ItemNodeInterface'
export class BubbleNode extends DecoratorBlockNode implements ItemNodeInterface {
__id: string
static getType(): string {
return 'snbubble'
}
static clone(node: BubbleNode): BubbleNode {
return new BubbleNode(node.__id, node.__format, node.__key)
}
static importJSON(serializedNode: SerializedBubbleNode): BubbleNode {
const node = $createBubbleNode(serializedNode.itemUuid)
node.setFormat(serializedNode.format)
return node
}
exportJSON(): SerializedBubbleNode {
return {
...super.exportJSON(),
itemUuid: this.getId(),
version: 1,
type: 'snbubble',
}
}
static importDOM(): DOMConversionMap<HTMLDivElement> | null {
return {
div: (domNode: HTMLDivElement) => {
if (!domNode.hasAttribute('data-lexical-item-uuid')) {
return null
}
return {
conversion: convertToBubbleElement,
priority: 2,
}
},
}
}
createDOM(): HTMLElement {
return document.createElement('span')
}
exportDOM(): DOMExportOutput {
const element = document.createElement('span')
element.setAttribute('data-lexical-item-uuid', this.__id)
const text = document.createTextNode(this.getTextContent())
element.append(text)
return { element }
}
constructor(id: string, format?: ElementFormatType, key?: NodeKey) {
super(format, key)
this.__id = id
}
getId(): string {
return this.__id
}
getTextContent(_includeInert?: boolean | undefined, _includeDirectionless?: false | undefined): string {
return `[Item: ${this.__id}]`
}
decorate(_editor: LexicalEditor): JSX.Element {
return <BubbleComponent node={this} itemUuid={this.__id} />
}
}

View File

@@ -0,0 +1,20 @@
import type { DOMConversionOutput, LexicalNode } from 'lexical'
import { BubbleNode } from './BubbleNode'
export function convertToBubbleElement(domNode: HTMLDivElement): DOMConversionOutput | null {
const itemUuid = domNode.getAttribute('data-lexical-item-uuid')
if (itemUuid) {
const node = $createBubbleNode(itemUuid)
return { node }
}
return null
}
export function $createBubbleNode(itemUuid: string): BubbleNode {
return new BubbleNode(itemUuid)
}
export function $isBubbleNode(node: BubbleNode | LexicalNode | null | undefined): node is BubbleNode {
return node instanceof BubbleNode
}

View File

@@ -0,0 +1,11 @@
import { Spread } from 'lexical'
import { SerializedDecoratorBlockNode } from '@lexical/react/LexicalDecoratorBlockNode'
export type SerializedBubbleNode = Spread<
{
itemUuid: string
version: 1
type: 'snbubble'
},
SerializedDecoratorBlockNode
>

View File

@@ -0,0 +1,3 @@
export interface ItemNodeInterface {
getId(): string
}

View File

@@ -1,18 +1,16 @@
import { FileItem } from '@standardnotes/snjs'
import { TypeaheadOption } from '@lexical/react/LexicalTypeaheadMenuPlugin'
import { LinkableItem } from '@/Utils/Items/Search/LinkableItem'
export class ItemOption extends TypeaheadOption {
icon?: JSX.Element
constructor(
public item: FileItem,
public item: LinkableItem,
public options: {
keywords?: Array<string>
keyboardShortcut?: string
onSelect: (queryString: string) => void
},
) {
super(item.title)
super(item.title || '')
this.key = item.uuid
}
}

View File

@@ -1,5 +1,5 @@
import LinkedItemMeta from '@/Components/LinkedItems/LinkedItemMeta'
import { PopoverItemClassNames, PopoverItemSelectedClassNames } from '@standardnotes/blocks-editor'
import { PopoverItemClassNames, PopoverItemSelectedClassNames } from '../ClassNames'
import { ItemOption } from './ItemOption'
type Props = {

View File

@@ -1,14 +1,16 @@
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { LexicalTypeaheadMenuPlugin, useBasicTypeaheadTriggerMatch } from '@lexical/react/LexicalTypeaheadMenuPlugin'
import { INSERT_FILE_COMMAND, PopoverClassNames } from '@standardnotes/blocks-editor'
import { TextNode } from 'lexical'
import { FunctionComponent, useCallback, useMemo, useState } from 'react'
import { ItemSelectionItemComponent } from './ItemSelectionItemComponent'
import { ItemOption } from './ItemOption'
import { useApplication } from '@/Components/ApplicationView/ApplicationProvider'
import { ContentType, FileItem, SNNote } from '@standardnotes/snjs'
import { ContentType, SNNote } from '@standardnotes/snjs'
import { getLinkingSearchResults } from '@/Utils/Items/Search/getSearchResults'
import Popover from '@/Components/Popover/Popover'
import { INSERT_BUBBLE_COMMAND, INSERT_FILE_COMMAND } from '../Commands'
import { useLinkingController } from '../../Contexts/LinkingControllerProvider'
import { PopoverClassNames } from '../ClassNames'
type Props = {
currentNote: SNNote
@@ -19,6 +21,8 @@ export const ItemSelectionPlugin: FunctionComponent<Props> = ({ currentNote }) =
const [editor] = useLexicalComposerContext()
const linkingController = useLinkingController()
const [queryString, setQueryString] = useState<string | null>('')
const checkForTriggerMatch = useBasicTypeaheadTriggerMatch('@', {
@@ -43,18 +47,24 @@ export const ItemSelectionPlugin: FunctionComponent<Props> = ({ currentNote }) =
const options = useMemo(() => {
const results = getLinkingSearchResults(queryString || '', application, currentNote, {
contentType: ContentType.File,
returnEmptyIfQueryEmpty: false,
})
const files = [...results.linkedItems, ...results.unlinkedItems] as FileItem[]
return files.map((file) => {
return new ItemOption(file, {
const items = [...results.linkedItems, ...results.unlinkedItems]
return items.map((item) => {
return new ItemOption(item, {
onSelect: (_queryString: string) => {
editor.dispatchCommand(INSERT_FILE_COMMAND, file.uuid)
void linkingController.linkItems(currentNote, item)
if (item.content_type === ContentType.File) {
editor.dispatchCommand(INSERT_FILE_COMMAND, item.uuid)
} else {
editor.dispatchCommand(INSERT_BUBBLE_COMMAND, item.uuid)
}
},
})
})
}, [application, editor, currentNote, queryString])
}, [application, editor, currentNote, queryString, linkingController])
return (
<LexicalTypeaheadMenuPlugin<ItemOption>
@@ -80,7 +90,6 @@ export const ItemSelectionPlugin: FunctionComponent<Props> = ({ currentNote }) =
x: anchorElementRef.current.offsetLeft,
y: anchorElementRef.current.offsetTop + anchorElementRef.current.offsetHeight,
}}
className={'min-h-80 h-80'}
open={popoverOpen}
togglePopover={() => {
setPopoverOpen((prevValue) => !prevValue)

View File

@@ -0,0 +1,45 @@
import { useEffect, useRef } from 'react'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { $getNodeByKey, Klass, LexicalNode } from 'lexical'
import { ItemNodeInterface } from '../ItemNodeInterface'
type NodeKey = string
type ItemUuid = string
type ObserverProps = {
nodeType: Klass<LexicalNode>
onRemove: (itemUuid: string) => void
}
export function NodeObserverPlugin({ nodeType, onRemove }: ObserverProps) {
const [editor] = useLexicalComposerContext()
const map = useRef<Map<NodeKey, ItemUuid>>(new Map())
useEffect(() => {
const removeMutationListener = editor.registerMutationListener(nodeType, (mutatedNodes) => {
editor.getEditorState().read(() => {
for (const [nodeKey, mutation] of mutatedNodes) {
if (mutation === 'updated' || mutation === 'created') {
const node = $getNodeByKey(nodeKey) as unknown as ItemNodeInterface
if (node) {
const uuid = node.getId()
map.current.set(nodeKey, uuid)
}
} else if (mutation === 'destroyed') {
const uuid = map.current.get(nodeKey)
if (uuid) {
onRemove(uuid)
}
}
}
})
})
return () => {
removeMutationListener()
}
})
return null
}

View File

@@ -15,11 +15,13 @@ type Props = {
link: ItemLink
activateItem: (item: LinkableItem) => Promise<void>
unlinkItem: LinkingController['unlinkItemFromSelectedItem']
focusPreviousItem: () => void
focusNextItem: () => void
focusedId: string | undefined
setFocusedId: (id: string) => void
focusPreviousItem?: () => void
focusNextItem?: () => void
focusedId?: string | undefined
setFocusedId?: (id: string) => void
isBidirectional: boolean
inlineFlex?: boolean
className?: string
}
const LinkedItemBubble = ({
@@ -31,6 +33,8 @@ const LinkedItemBubble = ({
focusedId,
setFocusedId,
isBidirectional,
inlineFlex,
className,
}: Props) => {
const ref = useRef<HTMLButtonElement>(null)
const application = useApplication()
@@ -42,7 +46,7 @@ const LinkedItemBubble = ({
const handleFocus = () => {
if (focusedId !== link.id) {
setFocusedId(link.id)
setFocusedId?.(link.id)
}
setShowUnlinkButton(true)
}
@@ -63,21 +67,21 @@ const LinkedItemBubble = ({
const onUnlinkClick: MouseEventHandler = (event) => {
event.stopPropagation()
void unlinkItem(link)
void unlinkItem(link.item)
}
const onKeyDown: KeyboardEventHandler = (event) => {
switch (event.key) {
case KeyboardKey.Backspace: {
focusPreviousItem()
void unlinkItem(link)
focusPreviousItem?.()
void unlinkItem(link.item)
break
}
case KeyboardKey.Left:
focusPreviousItem()
focusPreviousItem?.()
break
case KeyboardKey.Right:
focusNextItem()
focusNextItem?.()
break
}
}
@@ -94,7 +98,12 @@ const LinkedItemBubble = ({
return (
<button
ref={ref}
className="group flex h-6 cursor-pointer items-center rounded border-0 bg-passive-4-opacity-variant py-2 pl-1 pr-2 text-sm text-text hover:bg-contrast focus:bg-contrast lg:text-xs"
className={classNames(
'group h-6 cursor-pointer items-center rounded border-0 bg-passive-4-opacity-variant py-2 pl-1 pr-2 text-sm',
'text-text hover:bg-contrast focus:bg-contrast lg:text-xs',
inlineFlex ? 'inline-flex' : 'flex',
className,
)}
onFocus={handleFocus}
onBlur={onBlur}
onClick={onClick}

View File

@@ -279,7 +279,7 @@ const LinkedItemsPanel = ({
key={link.id}
item={link.item}
searchQuery={searchQuery}
unlinkItem={() => unlinkItemFromSelectedItem(link)}
unlinkItem={() => unlinkItemFromSelectedItem(link.item)}
activateItem={activateItem}
handleFileAction={filesController.handleFileAction}
/>
@@ -299,7 +299,7 @@ const LinkedItemsPanel = ({
key={link.id}
item={link.item}
searchQuery={searchQuery}
unlinkItem={() => unlinkItemFromSelectedItem(link)}
unlinkItem={() => unlinkItemFromSelectedItem(link.item)}
activateItem={activateItem}
handleFileAction={filesController.handleFileAction}
/>
@@ -330,7 +330,7 @@ const LinkedItemsPanel = ({
key={link.id}
item={link.item}
searchQuery={searchQuery}
unlinkItem={() => unlinkItemFromSelectedItem(link)}
unlinkItem={() => unlinkItemFromSelectedItem(link.item)}
activateItem={activateItem}
handleFileAction={filesController.handleFileAction}
/>
@@ -349,7 +349,7 @@ const LinkedItemsPanel = ({
key={link.id}
item={link.item}
searchQuery={searchQuery}
unlinkItem={() => unlinkItemFromSelectedItem(link)}
unlinkItem={() => unlinkItemFromSelectedItem(link.item)}
activateItem={activateItem}
handleFileAction={filesController.handleFileAction}
/>
@@ -366,7 +366,7 @@ const LinkedItemsPanel = ({
key={link.id}
item={link.item}
searchQuery={searchQuery}
unlinkItem={() => unlinkItemFromSelectedItem(link)}
unlinkItem={() => unlinkItemFromSelectedItem(link.item)}
activateItem={activateItem}
handleFileAction={filesController.handleFileAction}
/>
@@ -385,7 +385,7 @@ const LinkedItemsPanel = ({
key={link.id}
item={link.item}
searchQuery={searchQuery}
unlinkItem={() => unlinkItemFromSelectedItem(link)}
unlinkItem={() => unlinkItemFromSelectedItem(link.item)}
activateItem={activateItem}
handleFileAction={filesController.handleFileAction}
/>

View File

@@ -1091,7 +1091,9 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
</div>
)}
</div>
<LinkedItemBubblesContainer linkingController={this.viewControllerManager.linkingController} />
{editorMode !== 'blocks' && (
<LinkedItemBubblesContainer linkingController={this.viewControllerManager.linkingController} />
)}
</div>
)}
@@ -1148,7 +1150,12 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
{editorMode === 'blocks' && (
<div className={classNames('blocks-editor w-full flex-grow overflow-hidden overflow-y-scroll')}>
<BlockEditor key={this.note.uuid} application={this.application} note={this.note} />
<BlockEditor
key={this.note.uuid}
application={this.application}
note={this.note}
linkingController={this.viewControllerManager.linkingController}
/>
</div>
)}

View File

@@ -22,7 +22,7 @@ export const TAG_FOLDERS_FEATURE_TOOLTIP = 'A Plus or Pro plan is required to en
export const SMART_TAGS_FEATURE_NAME = 'Smart Tags'
export const PLAIN_EDITOR_NAME = 'Plain Text'
export const BLOCKS_EDITOR_NAME = 'Blocks'
export const BLOCKS_EDITOR_NAME = 'Super Note'
export const SYNC_TIMEOUT_DEBOUNCE = 350
export const SYNC_TIMEOUT_NO_DEBOUNCE = 100

View File

@@ -219,14 +219,14 @@ export class LinkingController extends AbstractViewController {
return undefined
}
unlinkItemFromSelectedItem = async (itemToUnlink: ItemLink) => {
unlinkItemFromSelectedItem = async (itemToUnlink: LinkableItem) => {
const selectedItem = this.selectionController.firstSelectedItem
if (!selectedItem) {
return
}
await this.application.items.unlinkItems(selectedItem, itemToUnlink.item)
await this.application.items.unlinkItems(selectedItem, itemToUnlink)
void this.application.sync.sync()
this.reloadAllLinks()

View File

@@ -11,7 +11,7 @@ import {
} from '@standardnotes/snjs'
import { EditorMenuGroup } from '@/Components/NotesOptions/EditorMenuGroup'
import { EditorMenuItem } from '@/Components/NotesOptions/EditorMenuItem'
import { PLAIN_EDITOR_NAME } from '@/Constants/Constants'
import { BLOCKS_EDITOR_NAME, PLAIN_EDITOR_NAME } from '@/Constants/Constants'
type NoteTypeToEditorRowsMap = Record<NoteType, EditorMenuItem[]>
@@ -128,7 +128,7 @@ const createGroupsFromMap = (map: NoteTypeToEditorRowsMap): EditorMenuGroup[] =>
groups.splice(1, 0, {
icon: 'dashboard',
iconClassName: 'text-accessory-tint-1',
title: 'Blocks',
title: BLOCKS_EDITOR_NAME,
items: map[NoteType.Blocks],
})
}
@@ -157,7 +157,7 @@ const createBaselineMap = (): NoteTypeToEditorRowsMap => {
if (featureTrunkEnabled(FeatureTrunkName.Blocks)) {
map[NoteType.Blocks].push({
name: 'Blocks',
name: BLOCKS_EDITOR_NAME,
isEntitled: true,
noteType: NoteType.Blocks,
})