2026-04-13 22:45:25 +02:00
|
|
|
import { useEffect, useCallback, useMemo, useRef, useContext } from 'react'
|
feat: implement PostHog event tracking plan
Add trackEvent() calls for all user actions in the tracking plan:
- Vault: vault_opened (with has_git, note_count), vault_switched
- Notes: note_created (with has_type, creation_path), note_deleted,
note_trashed, note_archived, note_favorited, note_unfavorited
- Git: commit_made, sync_triggered
- Search: search_used
- Editor: raw_mode_toggled, wikilink_inserted
- Types & Views: type_created, view_created
- Telemetry: telemetry_opted_in, telemetry_opted_out
All events gated by PostHog initialization (only fires when opted in).
No PII in any event properties — counts, booleans, and enums only.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 20:42:43 +02:00
|
|
|
import { trackEvent } from '../lib/telemetry'
|
2026-04-15 22:42:40 +02:00
|
|
|
import {
|
|
|
|
|
useCreateBlockNote,
|
|
|
|
|
SuggestionMenuController,
|
|
|
|
|
BlockNoteViewRaw,
|
|
|
|
|
ComponentsContext,
|
2026-04-24 23:25:22 +02:00
|
|
|
DeleteLinkButton,
|
|
|
|
|
EditLinkButton,
|
|
|
|
|
LinkToolbar,
|
|
|
|
|
LinkToolbarController,
|
2026-04-17 17:48:12 +02:00
|
|
|
SideMenuController,
|
2026-04-24 23:25:22 +02:00
|
|
|
useComponentsContext,
|
|
|
|
|
useDictionary,
|
|
|
|
|
type LinkToolbarProps,
|
2026-04-15 22:42:40 +02:00
|
|
|
} from '@blocknote/react'
|
2026-04-13 22:45:25 +02:00
|
|
|
import { components } from '@blocknote/mantine'
|
|
|
|
|
import { MantineContext, MantineProvider } from '@mantine/core'
|
2026-04-24 23:25:22 +02:00
|
|
|
import { ExternalLink } from 'lucide-react'
|
2026-04-24 22:26:07 +02:00
|
|
|
import { useDocumentThemeMode } from '../hooks/useDocumentThemeMode'
|
refactor: decompose Editor.tsx, NoteList.tsx, Sidebar.tsx — CodeScene hotspots (#126)
* refactor: decompose Editor.tsx into focused subcomponents and hooks
Extract from the monolithic Editor component (cc=35, 213 LoC, score 7.82):
- useDiffMode hook: diff state + toggle/commit-diff handlers
- useEditorFocus hook: new-note focus event listener
- editorSchema: WikiLink spec + BlockNote schema (module-level)
- SingleEditorView: BlockNote editor + suggestion menus
- EditorContent: breadcrumb bar + diff/editor/loading views
- EditorRightPanel: AI chat / Inspector panel switching
- suggestionEnrichment util: shared enrichment logic (eliminates duplication)
Editor.tsx is now 10.0 code health (was 7.82). All 25 existing tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract standalone functions from NoteList and Sidebar to reduce cc
NoteList: extract createNoteStatusResolver and toggleSetMember from
NoteListInner (cc 13→8, score 9.04→9.38).
Sidebar: extract applyCustomization from Sidebar (cc 10→7, score 9.09→10.0).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add tests for useDiffMode, useEditorFocus, and suggestionEnrichment
Cover extracted hook/utility logic: diff toggle/reset, editor focus event
listener with adaptive timing, and suggestion item enrichment pipeline.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: update architecture for editor decomposition and write .claude-done
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-27 15:27:25 +01:00
|
|
|
import { useEditorTheme } from '../hooks/useTheme'
|
|
|
|
|
import { useImageDrop } from '../hooks/useImageDrop'
|
|
|
|
|
import { buildTypeEntryMap } from '../utils/typeColors'
|
|
|
|
|
import { preFilterWikilinks, deduplicateByPath, MIN_QUERY_LENGTH } from '../utils/wikilinkSuggestions'
|
|
|
|
|
import { filterPersonMentions, PERSON_MENTION_MIN_QUERY } from '../utils/personMentionSuggestions'
|
|
|
|
|
import { attachClickHandlers, enrichSuggestionItems } from '../utils/suggestionEnrichment'
|
2026-04-24 23:25:22 +02:00
|
|
|
import { openExternalUrl } from '../utils/url'
|
2026-04-26 03:01:10 +02:00
|
|
|
import { observeNativeTextAssistanceDisabled } from '../lib/nativeTextAssistance'
|
2026-04-26 11:31:41 +02:00
|
|
|
import { getRuntimeStyleNonce } from '../lib/runtimeStyleNonce'
|
refactor: decompose Editor.tsx, NoteList.tsx, Sidebar.tsx — CodeScene hotspots (#126)
* refactor: decompose Editor.tsx into focused subcomponents and hooks
Extract from the monolithic Editor component (cc=35, 213 LoC, score 7.82):
- useDiffMode hook: diff state + toggle/commit-diff handlers
- useEditorFocus hook: new-note focus event listener
- editorSchema: WikiLink spec + BlockNote schema (module-level)
- SingleEditorView: BlockNote editor + suggestion menus
- EditorContent: breadcrumb bar + diff/editor/loading views
- EditorRightPanel: AI chat / Inspector panel switching
- suggestionEnrichment util: shared enrichment logic (eliminates duplication)
Editor.tsx is now 10.0 code health (was 7.82). All 25 existing tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract standalone functions from NoteList and Sidebar to reduce cc
NoteList: extract createNoteStatusResolver and toggleSetMember from
NoteListInner (cc 13→8, score 9.04→9.38).
Sidebar: extract applyCustomization from Sidebar (cc 10→7, score 9.09→10.0).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add tests for useDiffMode, useEditorFocus, and suggestionEnrichment
Cover extracted hook/utility logic: diff toggle/reset, editor focus event
listener with adaptive timing, and suggestion item enrichment pipeline.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: update architecture for editor decomposition and write .claude-done
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-27 15:27:25 +01:00
|
|
|
import { WikilinkSuggestionMenu, type WikilinkSuggestionItem } from './WikilinkSuggestionMenu'
|
|
|
|
|
import type { VaultEntry } from '../types'
|
|
|
|
|
import { _wikilinkEntriesRef } from './editorSchema'
|
2026-04-15 19:42:25 +02:00
|
|
|
import { useBlockNoteSideMenuHoverGuard } from './blockNoteSideMenuHoverGuard'
|
2026-04-15 22:42:40 +02:00
|
|
|
import { getTolariaSlashMenuItems } from './tolariaEditorFormattingConfig'
|
2026-04-16 11:24:37 +02:00
|
|
|
import {
|
|
|
|
|
TolariaFormattingToolbar,
|
|
|
|
|
TolariaFormattingToolbarController,
|
|
|
|
|
} from './tolariaEditorFormatting'
|
2026-04-17 17:48:12 +02:00
|
|
|
import { TolariaSideMenu } from './tolariaBlockNoteSideMenu'
|
2026-04-08 19:06:21 +02:00
|
|
|
import { useEditorLinkActivation } from './useEditorLinkActivation'
|
2026-04-25 21:21:41 +02:00
|
|
|
import { findNearestTextCursorBlock } from './blockNoteCursorTarget'
|
refactor: decompose Editor.tsx, NoteList.tsx, Sidebar.tsx — CodeScene hotspots (#126)
* refactor: decompose Editor.tsx into focused subcomponents and hooks
Extract from the monolithic Editor component (cc=35, 213 LoC, score 7.82):
- useDiffMode hook: diff state + toggle/commit-diff handlers
- useEditorFocus hook: new-note focus event listener
- editorSchema: WikiLink spec + BlockNote schema (module-level)
- SingleEditorView: BlockNote editor + suggestion menus
- EditorContent: breadcrumb bar + diff/editor/loading views
- EditorRightPanel: AI chat / Inspector panel switching
- suggestionEnrichment util: shared enrichment logic (eliminates duplication)
Editor.tsx is now 10.0 code health (was 7.82). All 25 existing tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract standalone functions from NoteList and Sidebar to reduce cc
NoteList: extract createNoteStatusResolver and toggleSetMember from
NoteListInner (cc 13→8, score 9.04→9.38).
Sidebar: extract applyCustomization from Sidebar (cc 10→7, score 9.09→10.0).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add tests for useDiffMode, useEditorFocus, and suggestionEnrichment
Cover extracted hook/utility logic: diff toggle/reset, editor focus event
listener with adaptive timing, and suggestion item enrichment pipeline.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: update architecture for editor decomposition and write .claude-done
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-27 15:27:25 +01:00
|
|
|
|
2026-04-12 11:45:59 +02:00
|
|
|
const TEST_TABLE_MARKDOWN = `| Head 1 | Head 2 | Head 3 |
|
|
|
|
|
| --- | --- | --- |
|
|
|
|
|
| A | B | C |
|
|
|
|
|
| D | E | F |
|
|
|
|
|
`
|
2026-04-24 23:25:22 +02:00
|
|
|
const CONTAINER_CLICK_IGNORE_SELECTOR = [
|
|
|
|
|
'[contenteditable="true"]',
|
|
|
|
|
'.bn-formatting-toolbar',
|
|
|
|
|
'.bn-link-toolbar',
|
2026-04-26 01:20:11 +02:00
|
|
|
'.bn-side-menu',
|
2026-04-24 23:25:22 +02:00
|
|
|
'.bn-form-popover',
|
|
|
|
|
'[role="menu"]',
|
|
|
|
|
'[role="dialog"]',
|
|
|
|
|
].join(', ')
|
|
|
|
|
const TOOLBAR_MOUSE_DOWN_ALLOW_SELECTOR = [
|
|
|
|
|
'[role="menu"]',
|
|
|
|
|
'[role="dialog"]',
|
|
|
|
|
'button[aria-haspopup]',
|
|
|
|
|
'input',
|
|
|
|
|
'textarea',
|
|
|
|
|
'[contenteditable="true"]',
|
|
|
|
|
].join(', ')
|
2026-04-12 11:45:59 +02:00
|
|
|
|
|
|
|
|
type TestTableBlock = {
|
|
|
|
|
type?: string
|
|
|
|
|
content?: { type?: string; columnWidths?: Array<number | null> }
|
|
|
|
|
}
|
2026-04-28 23:29:09 +02:00
|
|
|
type SuggestionAction = () => void
|
|
|
|
|
type SuggestionItemWithClick = { onItemClick?: SuggestionAction }
|
|
|
|
|
|
|
|
|
|
function isEditorReadyForSuggestionAction(
|
|
|
|
|
editor: ReturnType<typeof useCreateBlockNote>,
|
|
|
|
|
container: HTMLElement | null,
|
|
|
|
|
) {
|
|
|
|
|
if (!container?.isConnected) return false
|
|
|
|
|
|
|
|
|
|
const editorElement = editor.domElement
|
|
|
|
|
if (!(editorElement instanceof HTMLElement)) return true
|
|
|
|
|
|
|
|
|
|
return editorElement.isConnected && container.contains(editorElement)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function runSuggestionActionSafely({
|
|
|
|
|
action,
|
|
|
|
|
container,
|
|
|
|
|
editor,
|
|
|
|
|
}: {
|
|
|
|
|
action: SuggestionAction
|
|
|
|
|
container: HTMLElement | null
|
|
|
|
|
editor: ReturnType<typeof useCreateBlockNote>
|
|
|
|
|
}) {
|
|
|
|
|
if (!isEditorReadyForSuggestionAction(editor, container)) return
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
action()
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.warn('[editor] Ignored stale suggestion menu action:', error)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function guardSuggestionMenuItems<T extends SuggestionItemWithClick>(
|
|
|
|
|
items: T[],
|
|
|
|
|
runEditorAction: (action: SuggestionAction) => void,
|
|
|
|
|
): T[] {
|
|
|
|
|
return items.map((item) => {
|
|
|
|
|
if (!item.onItemClick) return item
|
|
|
|
|
|
|
|
|
|
const onItemClick = item.onItemClick
|
|
|
|
|
return {
|
|
|
|
|
...item,
|
|
|
|
|
onItemClick: () => runEditorAction(onItemClick),
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
2026-04-12 11:45:59 +02:00
|
|
|
|
2026-04-13 22:45:25 +02:00
|
|
|
function SharedContextBlockNoteView(props: React.ComponentProps<typeof BlockNoteViewRaw>) {
|
2026-04-15 22:42:40 +02:00
|
|
|
const { children, className, theme, ...rest } = props
|
2026-04-13 22:45:25 +02:00
|
|
|
const mantineContext = useContext(MantineContext)
|
2026-04-14 11:14:49 +02:00
|
|
|
const colorScheme = theme === 'dark' ? 'dark' : 'light'
|
2026-04-13 22:45:25 +02:00
|
|
|
const view = (
|
|
|
|
|
<ComponentsContext.Provider value={components}>
|
2026-04-14 11:14:49 +02:00
|
|
|
<BlockNoteViewRaw
|
|
|
|
|
{...rest}
|
|
|
|
|
className={['bn-mantine', className].filter(Boolean).join(' ')}
|
|
|
|
|
data-mantine-color-scheme={colorScheme}
|
|
|
|
|
theme={theme}
|
2026-04-15 22:42:40 +02:00
|
|
|
>
|
|
|
|
|
{children}
|
|
|
|
|
</BlockNoteViewRaw>
|
2026-04-13 22:45:25 +02:00
|
|
|
</ComponentsContext.Provider>
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (mantineContext) return view
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<MantineProvider
|
2026-04-14 11:14:49 +02:00
|
|
|
// BlockNote scopes Mantine defaults under `.bn-mantine` instead of `:root`.
|
2026-04-13 22:45:25 +02:00
|
|
|
withCssVariables={false}
|
2026-04-26 11:31:41 +02:00
|
|
|
getStyleNonce={getRuntimeStyleNonce}
|
2026-04-13 22:45:25 +02:00
|
|
|
getRootElement={() => undefined}
|
|
|
|
|
>
|
|
|
|
|
{view}
|
|
|
|
|
</MantineProvider>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-24 23:25:22 +02:00
|
|
|
function shouldAllowToolbarMouseDown(target: HTMLElement) {
|
|
|
|
|
return Boolean(target.closest(TOOLBAR_MOUSE_DOWN_ALLOW_SELECTOR))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleToolbarMouseDownCapture(
|
|
|
|
|
event: Pick<React.MouseEvent<HTMLElement>, 'target' | 'preventDefault'>,
|
|
|
|
|
) {
|
|
|
|
|
if (!(event.target instanceof HTMLElement) || shouldAllowToolbarMouseDown(event.target)) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
event.preventDefault()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function TolariaOpenLinkButton({ url }: Pick<LinkToolbarProps, 'url'>) {
|
|
|
|
|
const Components = useComponentsContext()!
|
|
|
|
|
const dict = useDictionary()
|
|
|
|
|
const handleOpen = useCallback(() => {
|
|
|
|
|
void openExternalUrl(url).catch((error) => {
|
|
|
|
|
console.warn('[link] Failed to open URL from toolbar:', error)
|
|
|
|
|
})
|
|
|
|
|
}, [url])
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Components.LinkToolbar.Button
|
|
|
|
|
className="bn-button"
|
|
|
|
|
label={dict.link_toolbar.open.tooltip}
|
|
|
|
|
mainTooltip={dict.link_toolbar.open.tooltip}
|
|
|
|
|
isSelected={false}
|
|
|
|
|
onClick={handleOpen}
|
|
|
|
|
icon={<ExternalLink size={16} />}
|
|
|
|
|
/>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function TolariaLinkToolbar(props: LinkToolbarProps) {
|
|
|
|
|
return (
|
|
|
|
|
<LinkToolbar {...props}>
|
|
|
|
|
<EditLinkButton
|
|
|
|
|
url={props.url}
|
|
|
|
|
text={props.text}
|
|
|
|
|
range={props.range}
|
|
|
|
|
setToolbarOpen={props.setToolbarOpen}
|
|
|
|
|
setToolbarPositionFrozen={props.setToolbarPositionFrozen}
|
|
|
|
|
/>
|
|
|
|
|
<TolariaOpenLinkButton url={props.url} />
|
|
|
|
|
<DeleteLinkButton
|
|
|
|
|
range={props.range}
|
|
|
|
|
setToolbarOpen={props.setToolbarOpen}
|
|
|
|
|
/>
|
|
|
|
|
</LinkToolbar>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-12 11:45:59 +02:00
|
|
|
function applySeededColumnWidths(
|
|
|
|
|
parsedBlocks: Array<TestTableBlock>,
|
|
|
|
|
columnWidths?: Array<number | null>,
|
|
|
|
|
) {
|
2026-04-13 22:45:25 +02:00
|
|
|
if (!columnWidths) return
|
|
|
|
|
|
2026-04-12 11:45:59 +02:00
|
|
|
const tableBlock = parsedBlocks[0]
|
2026-04-13 22:45:25 +02:00
|
|
|
if (tableBlock?.type !== 'table') return
|
2026-04-12 12:29:13 +02:00
|
|
|
|
2026-04-13 22:45:25 +02:00
|
|
|
const tableContent = tableBlock.content
|
|
|
|
|
if (tableContent?.type !== 'tableContent') return
|
2026-04-12 11:45:59 +02:00
|
|
|
|
2026-04-12 12:29:13 +02:00
|
|
|
tableContent.columnWidths = [...columnWidths]
|
2026-04-12 11:45:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function seedEditorWithTestTable(
|
|
|
|
|
editor: ReturnType<typeof useCreateBlockNote>,
|
|
|
|
|
columnWidths?: Array<number | null>,
|
|
|
|
|
) {
|
|
|
|
|
const parsedBlocks = await Promise.resolve(
|
|
|
|
|
editor.tryParseMarkdownToBlocks(TEST_TABLE_MARKDOWN),
|
|
|
|
|
) as Array<TestTableBlock>
|
|
|
|
|
|
|
|
|
|
applySeededColumnWidths(parsedBlocks, columnWidths)
|
|
|
|
|
|
|
|
|
|
const tableHtml = editor.blocksToHTMLLossy([
|
|
|
|
|
...parsedBlocks,
|
|
|
|
|
{ type: 'paragraph', content: [], children: [] },
|
|
|
|
|
] as typeof editor.document)
|
|
|
|
|
editor._tiptapEditor.commands.setContent(tableHtml)
|
|
|
|
|
editor.focus()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function useSeedBlockNoteTableBridge(editor: ReturnType<typeof useCreateBlockNote>) {
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const seedBlockNoteTable = (columnWidths?: Array<number | null>) => (
|
|
|
|
|
seedEditorWithTestTable(editor, columnWidths)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
window.__laputaTest = {
|
|
|
|
|
...window.__laputaTest,
|
|
|
|
|
seedBlockNoteTable,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
if (window.__laputaTest?.seedBlockNoteTable === seedBlockNoteTable) {
|
|
|
|
|
delete window.__laputaTest.seedBlockNoteTable
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}, [editor])
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-16 11:24:37 +02:00
|
|
|
function shouldIgnoreContainerClick(target: HTMLElement) {
|
2026-04-24 23:25:22 +02:00
|
|
|
return Boolean(target.closest(CONTAINER_CLICK_IGNORE_SELECTOR))
|
2026-04-16 11:24:37 +02:00
|
|
|
}
|
|
|
|
|
|
2026-04-20 18:35:50 +02:00
|
|
|
function normalizeSuggestionQuery(query: string, triggerCharacter: string): string {
|
|
|
|
|
return query.startsWith(triggerCharacter)
|
|
|
|
|
? query.slice(triggerCharacter.length)
|
|
|
|
|
: query
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isSelectionInsideElement(element: HTMLElement): boolean {
|
|
|
|
|
const selection = window.getSelection()
|
|
|
|
|
const anchorNode = selection?.anchorNode ?? null
|
|
|
|
|
const anchorElement = anchorNode instanceof Element ? anchorNode : anchorNode?.parentElement ?? null
|
|
|
|
|
return Boolean(anchorElement && element.contains(anchorElement))
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-24 20:13:34 +02:00
|
|
|
const TITLE_HEADING_SELECTOR = 'h1, [data-content-type="heading"][data-level="1"], [data-content-type="heading"]:not([data-level])'
|
|
|
|
|
const TITLE_HEADING_WRAPPER_SELECTOR = '.bn-block-outer, .bn-block'
|
2026-04-27 18:31:51 +02:00
|
|
|
const CODE_BLOCK_SELECTOR = '[data-content-type="codeBlock"]'
|
|
|
|
|
|
|
|
|
|
function nodeElement(node: Node | null): HTMLElement | null {
|
|
|
|
|
if (!node) return null
|
|
|
|
|
if (node instanceof HTMLElement) return node
|
|
|
|
|
return node.parentElement
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function hasSingleActiveRange(selection: Selection | null): selection is Selection {
|
|
|
|
|
return Boolean(selection && selection.rangeCount === 1 && !selection.isCollapsed)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function closestCodeBlockInContainer(options: {
|
|
|
|
|
range: Range
|
|
|
|
|
container: HTMLElement
|
|
|
|
|
}): HTMLElement | null {
|
|
|
|
|
const { range, container } = options
|
|
|
|
|
const codeBlock = nodeElement(range.commonAncestorContainer)
|
|
|
|
|
?.closest<HTMLElement>(CODE_BLOCK_SELECTOR)
|
|
|
|
|
|
|
|
|
|
return codeBlock && container.contains(codeBlock) ? codeBlock : null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function nodeBelongsToElement(node: Node, element: HTMLElement): boolean {
|
|
|
|
|
const elementNode = nodeElement(node)
|
|
|
|
|
return Boolean(elementNode && element.contains(elementNode))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function rangeBelongsToElement(range: Range, element: HTMLElement): boolean {
|
|
|
|
|
return nodeBelongsToElement(range.startContainer, element)
|
|
|
|
|
&& nodeBelongsToElement(range.endContainer, element)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function selectedCodeBlockRange(options: {
|
|
|
|
|
selection: Selection | null
|
|
|
|
|
container: HTMLElement
|
|
|
|
|
}): Range | null {
|
|
|
|
|
const { selection, container } = options
|
|
|
|
|
if (!hasSingleActiveRange(selection)) return null
|
|
|
|
|
|
|
|
|
|
const range = selection.getRangeAt(0)
|
|
|
|
|
const codeBlock = closestCodeBlockInContainer({ range, container })
|
|
|
|
|
if (!codeBlock || !rangeBelongsToElement(range, codeBlock)) return null
|
|
|
|
|
|
|
|
|
|
return range
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function selectedCodeBlockText(options: {
|
|
|
|
|
selection: Selection | null
|
|
|
|
|
container: HTMLElement
|
|
|
|
|
}): string | null {
|
|
|
|
|
const range = selectedCodeBlockRange(options)
|
|
|
|
|
if (!range) return null
|
|
|
|
|
|
|
|
|
|
return options.selection?.toString() || range.cloneContents().textContent || ''
|
|
|
|
|
}
|
2026-04-24 20:13:34 +02:00
|
|
|
|
|
|
|
|
function findTitleHeadingElement(target: HTMLElement): HTMLElement | null {
|
|
|
|
|
const directHeading = target.closest<HTMLElement>(TITLE_HEADING_SELECTOR)
|
|
|
|
|
if (directHeading) return directHeading
|
|
|
|
|
|
|
|
|
|
const titleWrapper = target.closest<HTMLElement>(TITLE_HEADING_WRAPPER_SELECTOR)
|
|
|
|
|
return titleWrapper?.querySelector<HTMLElement>(TITLE_HEADING_SELECTOR) ?? null
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-20 18:35:50 +02:00
|
|
|
function queueTitleHeadingCursorRepair(
|
|
|
|
|
target: HTMLElement,
|
|
|
|
|
editor: ReturnType<typeof useCreateBlockNote>,
|
|
|
|
|
): boolean {
|
2026-04-24 20:13:34 +02:00
|
|
|
const titleHeading = findTitleHeadingElement(target)
|
2026-04-20 18:35:50 +02:00
|
|
|
if (!titleHeading) return false
|
|
|
|
|
|
|
|
|
|
queueMicrotask(() => {
|
|
|
|
|
if (isSelectionInsideElement(titleHeading)) return
|
|
|
|
|
|
|
|
|
|
const firstBlock = editor.document[0]
|
|
|
|
|
if (firstBlock?.type !== 'heading') return
|
|
|
|
|
|
2026-04-25 21:21:41 +02:00
|
|
|
try {
|
|
|
|
|
editor.setTextCursorPosition(firstBlock.id, 'end')
|
|
|
|
|
} catch {
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-04-20 18:35:50 +02:00
|
|
|
editor.focus()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function useEditorContainerClickHandler(options: {
|
|
|
|
|
editable: boolean
|
|
|
|
|
editor: ReturnType<typeof useCreateBlockNote>
|
|
|
|
|
}) {
|
|
|
|
|
const { editable, editor } = options
|
|
|
|
|
|
|
|
|
|
return useCallback((e: React.MouseEvent<HTMLDivElement>) => {
|
|
|
|
|
if (!editable) return
|
|
|
|
|
const target = e.target as HTMLElement
|
|
|
|
|
if (queueTitleHeadingCursorRepair(target, editor)) return
|
|
|
|
|
if (shouldIgnoreContainerClick(target)) return
|
|
|
|
|
const blocks = editor.document
|
|
|
|
|
if (blocks.length > 0) {
|
2026-04-25 21:21:41 +02:00
|
|
|
const targetBlock = findNearestTextCursorBlock(blocks, blocks.length - 1)
|
|
|
|
|
if (targetBlock) {
|
|
|
|
|
try {
|
|
|
|
|
editor.setTextCursorPosition(targetBlock.id, 'end')
|
|
|
|
|
} catch {
|
|
|
|
|
// Ignore transient BlockNote selection errors and at least restore focus.
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-20 18:35:50 +02:00
|
|
|
}
|
|
|
|
|
editor.focus()
|
|
|
|
|
}, [editor, editable])
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-24 02:50:12 +02:00
|
|
|
function useCompositionAwareEditorChange(options: {
|
|
|
|
|
containerRef: React.RefObject<HTMLDivElement | null>
|
|
|
|
|
onChange?: () => void
|
|
|
|
|
}) {
|
|
|
|
|
const { containerRef, onChange } = options
|
|
|
|
|
const onChangeRef = useRef(onChange)
|
|
|
|
|
const composingRef = useRef(false)
|
|
|
|
|
const pendingChangeRef = useRef(false)
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
onChangeRef.current = onChange
|
|
|
|
|
}, [onChange])
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const container = containerRef.current
|
|
|
|
|
if (!container) return
|
|
|
|
|
|
|
|
|
|
const flushPendingChange = () => {
|
|
|
|
|
if (composingRef.current || !pendingChangeRef.current) return
|
|
|
|
|
pendingChangeRef.current = false
|
|
|
|
|
onChangeRef.current?.()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleCompositionStart = () => {
|
|
|
|
|
composingRef.current = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleCompositionEnd = () => {
|
|
|
|
|
composingRef.current = false
|
|
|
|
|
queueMicrotask(flushPendingChange)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
container.addEventListener('compositionstart', handleCompositionStart, true)
|
|
|
|
|
container.addEventListener('compositionend', handleCompositionEnd, true)
|
|
|
|
|
return () => {
|
|
|
|
|
container.removeEventListener('compositionstart', handleCompositionStart, true)
|
|
|
|
|
container.removeEventListener('compositionend', handleCompositionEnd, true)
|
|
|
|
|
}
|
|
|
|
|
}, [containerRef])
|
|
|
|
|
|
|
|
|
|
return useCallback(() => {
|
|
|
|
|
if (composingRef.current) {
|
|
|
|
|
pendingChangeRef.current = true
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pendingChangeRef.current = false
|
|
|
|
|
onChangeRef.current?.()
|
|
|
|
|
}, [])
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-27 18:31:51 +02:00
|
|
|
function handleCodeBlockCopy(event: React.ClipboardEvent<HTMLDivElement>) {
|
|
|
|
|
const codeText = selectedCodeBlockText({
|
|
|
|
|
selection: window.getSelection(),
|
|
|
|
|
container: event.currentTarget,
|
|
|
|
|
})
|
|
|
|
|
if (codeText === null) return
|
|
|
|
|
|
|
|
|
|
event.clipboardData.setData('text/plain', codeText)
|
|
|
|
|
event.preventDefault()
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-20 18:35:50 +02:00
|
|
|
function buildBaseSuggestionItems(entries: VaultEntry[]) {
|
|
|
|
|
return deduplicateByPath(entries.map(entry => ({
|
|
|
|
|
title: entry.title,
|
|
|
|
|
aliases: [...new Set([entry.filename.replace(/\.md$/, ''), ...entry.aliases])],
|
|
|
|
|
group: entry.isA || 'Note',
|
|
|
|
|
entryType: entry.isA,
|
|
|
|
|
entryTitle: entry.title,
|
|
|
|
|
path: entry.path,
|
|
|
|
|
})))
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-28 23:29:09 +02:00
|
|
|
function useInsertWikilink(
|
|
|
|
|
editor: ReturnType<typeof useCreateBlockNote>,
|
|
|
|
|
runEditorAction: (action: SuggestionAction) => void,
|
|
|
|
|
) {
|
2026-04-20 18:35:50 +02:00
|
|
|
return useCallback((target: string) => {
|
2026-04-28 23:29:09 +02:00
|
|
|
runEditorAction(() => {
|
|
|
|
|
editor.insertInlineContent([
|
|
|
|
|
{ type: 'wikilink' as const, props: { target } },
|
|
|
|
|
" ",
|
|
|
|
|
], { updateSelection: true })
|
|
|
|
|
trackEvent('wikilink_inserted')
|
|
|
|
|
})
|
|
|
|
|
}, [editor, runEditorAction])
|
2026-04-20 18:35:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function useSuggestionMenuItems(options: {
|
|
|
|
|
baseItems: ReturnType<typeof buildBaseSuggestionItems>
|
|
|
|
|
editor: ReturnType<typeof useCreateBlockNote>
|
|
|
|
|
insertWikilink: (target: string) => void
|
2026-04-28 23:29:09 +02:00
|
|
|
runEditorAction: (action: SuggestionAction) => void
|
2026-04-20 18:35:50 +02:00
|
|
|
typeEntryMap: Record<string, VaultEntry>
|
|
|
|
|
vaultPath?: string
|
|
|
|
|
}) {
|
|
|
|
|
const {
|
|
|
|
|
baseItems,
|
|
|
|
|
editor,
|
|
|
|
|
insertWikilink,
|
2026-04-28 23:29:09 +02:00
|
|
|
runEditorAction,
|
2026-04-20 18:35:50 +02:00
|
|
|
typeEntryMap,
|
|
|
|
|
vaultPath,
|
|
|
|
|
} = options
|
|
|
|
|
|
|
|
|
|
const buildItems = useCallback((query: string, triggerCharacter: '[[' | '@') => {
|
|
|
|
|
const normalizedQuery = normalizeSuggestionQuery(query, triggerCharacter)
|
|
|
|
|
const minLength = triggerCharacter === '[[' ? MIN_QUERY_LENGTH : PERSON_MENTION_MIN_QUERY
|
|
|
|
|
if (normalizedQuery.length < minLength) return null
|
|
|
|
|
|
|
|
|
|
const candidates = triggerCharacter === '[['
|
|
|
|
|
? preFilterWikilinks(baseItems, normalizedQuery)
|
|
|
|
|
: filterPersonMentions(baseItems, normalizedQuery)
|
|
|
|
|
|
|
|
|
|
const items = attachClickHandlers(candidates, insertWikilink, vaultPath ?? '')
|
2026-04-28 23:29:09 +02:00
|
|
|
return guardSuggestionMenuItems(
|
|
|
|
|
enrichSuggestionItems(items, normalizedQuery, typeEntryMap),
|
|
|
|
|
runEditorAction,
|
|
|
|
|
)
|
|
|
|
|
}, [baseItems, insertWikilink, runEditorAction, typeEntryMap, vaultPath])
|
2026-04-20 18:35:50 +02:00
|
|
|
|
|
|
|
|
const getWikilinkItems = useCallback(async (query: string): Promise<WikilinkSuggestionItem[]> => (
|
|
|
|
|
buildItems(query, '[[') ?? []
|
|
|
|
|
), [buildItems])
|
|
|
|
|
|
|
|
|
|
const getPersonMentionItems = useCallback(async (query: string): Promise<WikilinkSuggestionItem[]> => (
|
|
|
|
|
buildItems(query, '@') ?? []
|
|
|
|
|
), [buildItems])
|
|
|
|
|
|
2026-04-28 23:29:09 +02:00
|
|
|
const getSlashMenuItems = useCallback(async (query: string) => {
|
|
|
|
|
try {
|
|
|
|
|
return guardSuggestionMenuItems(
|
|
|
|
|
await Promise.resolve(getTolariaSlashMenuItems(editor, query)),
|
|
|
|
|
runEditorAction,
|
|
|
|
|
)
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.warn('[editor] Ignored stale slash menu query:', error)
|
|
|
|
|
return []
|
|
|
|
|
}
|
|
|
|
|
}, [editor, runEditorAction])
|
2026-04-20 18:35:50 +02:00
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
getWikilinkItems,
|
|
|
|
|
getPersonMentionItems,
|
|
|
|
|
getSlashMenuItems,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-28 21:31:47 +01:00
|
|
|
/** Insert an image block after the current cursor position. */
|
|
|
|
|
function useInsertImageCallback(editor: ReturnType<typeof useCreateBlockNote>) {
|
|
|
|
|
const editorRef = useRef(editor)
|
|
|
|
|
useEffect(() => { editorRef.current = editor }, [editor])
|
|
|
|
|
return useCallback((url: string) => {
|
|
|
|
|
const e = editorRef.current
|
|
|
|
|
const cursorBlock = e.getTextCursorPosition().block
|
|
|
|
|
e.insertBlocks([{ type: 'image' as const, props: { url } }], cursorBlock, 'after')
|
|
|
|
|
}, [])
|
|
|
|
|
}
|
|
|
|
|
|
refactor: decompose Editor.tsx, NoteList.tsx, Sidebar.tsx — CodeScene hotspots (#126)
* refactor: decompose Editor.tsx into focused subcomponents and hooks
Extract from the monolithic Editor component (cc=35, 213 LoC, score 7.82):
- useDiffMode hook: diff state + toggle/commit-diff handlers
- useEditorFocus hook: new-note focus event listener
- editorSchema: WikiLink spec + BlockNote schema (module-level)
- SingleEditorView: BlockNote editor + suggestion menus
- EditorContent: breadcrumb bar + diff/editor/loading views
- EditorRightPanel: AI chat / Inspector panel switching
- suggestionEnrichment util: shared enrichment logic (eliminates duplication)
Editor.tsx is now 10.0 code health (was 7.82). All 25 existing tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract standalone functions from NoteList and Sidebar to reduce cc
NoteList: extract createNoteStatusResolver and toggleSetMember from
NoteListInner (cc 13→8, score 9.04→9.38).
Sidebar: extract applyCustomization from Sidebar (cc 10→7, score 9.09→10.0).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add tests for useDiffMode, useEditorFocus, and suggestionEnrichment
Cover extracted hook/utility logic: diff toggle/reset, editor focus event
listener with adaptive timing, and suggestion item enrichment pipeline.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: update architecture for editor decomposition and write .claude-done
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-27 15:27:25 +01:00
|
|
|
/** Single BlockNote editor view — content is swapped via replaceBlocks */
|
refactor: remove theming system entirely
Remove all theme switching, creation, and management functionality
from the app — backend (Rust theme module, Tauri commands, menu items,
startup tasks, vault seeding), frontend (useThemeManager, ThemePropertyEditor,
themeSchema, command palette Appearance group, settings panel appearance
section, isDarkTheme prop chain), mock data, and all related tests.
Editor styling via theme.json/EditorTheme.css is preserved (not user theming).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 19:57:58 +01:00
|
|
|
export function SingleEditorView({ editor, entries, onNavigateWikilink, onChange, vaultPath, editable = true }: {
|
refactor: decompose Editor.tsx, NoteList.tsx, Sidebar.tsx — CodeScene hotspots (#126)
* refactor: decompose Editor.tsx into focused subcomponents and hooks
Extract from the monolithic Editor component (cc=35, 213 LoC, score 7.82):
- useDiffMode hook: diff state + toggle/commit-diff handlers
- useEditorFocus hook: new-note focus event listener
- editorSchema: WikiLink spec + BlockNote schema (module-level)
- SingleEditorView: BlockNote editor + suggestion menus
- EditorContent: breadcrumb bar + diff/editor/loading views
- EditorRightPanel: AI chat / Inspector panel switching
- suggestionEnrichment util: shared enrichment logic (eliminates duplication)
Editor.tsx is now 10.0 code health (was 7.82). All 25 existing tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract standalone functions from NoteList and Sidebar to reduce cc
NoteList: extract createNoteStatusResolver and toggleSetMember from
NoteListInner (cc 13→8, score 9.04→9.38).
Sidebar: extract applyCustomization from Sidebar (cc 10→7, score 9.09→10.0).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add tests for useDiffMode, useEditorFocus, and suggestionEnrichment
Cover extracted hook/utility logic: diff toggle/reset, editor focus event
listener with adaptive timing, and suggestion item enrichment pipeline.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: update architecture for editor decomposition and write .claude-done
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-27 15:27:25 +01:00
|
|
|
editor: ReturnType<typeof useCreateBlockNote>
|
|
|
|
|
entries: VaultEntry[]
|
|
|
|
|
onNavigateWikilink: (target: string) => void
|
|
|
|
|
onChange?: () => void
|
2026-02-28 21:31:47 +01:00
|
|
|
vaultPath?: string
|
2026-03-03 01:37:25 +01:00
|
|
|
editable?: boolean
|
refactor: decompose Editor.tsx, NoteList.tsx, Sidebar.tsx — CodeScene hotspots (#126)
* refactor: decompose Editor.tsx into focused subcomponents and hooks
Extract from the monolithic Editor component (cc=35, 213 LoC, score 7.82):
- useDiffMode hook: diff state + toggle/commit-diff handlers
- useEditorFocus hook: new-note focus event listener
- editorSchema: WikiLink spec + BlockNote schema (module-level)
- SingleEditorView: BlockNote editor + suggestion menus
- EditorContent: breadcrumb bar + diff/editor/loading views
- EditorRightPanel: AI chat / Inspector panel switching
- suggestionEnrichment util: shared enrichment logic (eliminates duplication)
Editor.tsx is now 10.0 code health (was 7.82). All 25 existing tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract standalone functions from NoteList and Sidebar to reduce cc
NoteList: extract createNoteStatusResolver and toggleSetMember from
NoteListInner (cc 13→8, score 9.04→9.38).
Sidebar: extract applyCustomization from Sidebar (cc 10→7, score 9.09→10.0).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add tests for useDiffMode, useEditorFocus, and suggestionEnrichment
Cover extracted hook/utility logic: diff toggle/reset, editor focus event
listener with adaptive timing, and suggestion item enrichment pipeline.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: update architecture for editor decomposition and write .claude-done
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-27 15:27:25 +01:00
|
|
|
}) {
|
|
|
|
|
const { cssVars } = useEditorTheme()
|
2026-04-24 22:26:07 +02:00
|
|
|
const themeMode = useDocumentThemeMode()
|
refactor: decompose Editor.tsx, NoteList.tsx, Sidebar.tsx — CodeScene hotspots (#126)
* refactor: decompose Editor.tsx into focused subcomponents and hooks
Extract from the monolithic Editor component (cc=35, 213 LoC, score 7.82):
- useDiffMode hook: diff state + toggle/commit-diff handlers
- useEditorFocus hook: new-note focus event listener
- editorSchema: WikiLink spec + BlockNote schema (module-level)
- SingleEditorView: BlockNote editor + suggestion menus
- EditorContent: breadcrumb bar + diff/editor/loading views
- EditorRightPanel: AI chat / Inspector panel switching
- suggestionEnrichment util: shared enrichment logic (eliminates duplication)
Editor.tsx is now 10.0 code health (was 7.82). All 25 existing tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract standalone functions from NoteList and Sidebar to reduce cc
NoteList: extract createNoteStatusResolver and toggleSetMember from
NoteListInner (cc 13→8, score 9.04→9.38).
Sidebar: extract applyCustomization from Sidebar (cc 10→7, score 9.09→10.0).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add tests for useDiffMode, useEditorFocus, and suggestionEnrichment
Cover extracted hook/utility logic: diff toggle/reset, editor focus event
listener with adaptive timing, and suggestion item enrichment pipeline.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: update architecture for editor decomposition and write .claude-done
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-27 15:27:25 +01:00
|
|
|
const containerRef = useRef<HTMLDivElement>(null)
|
2026-04-20 18:35:50 +02:00
|
|
|
const handleContainerClick = useEditorContainerClickHandler({ editable, editor })
|
2026-04-24 02:50:12 +02:00
|
|
|
const handleEditorChange = useCompositionAwareEditorChange({ containerRef, onChange })
|
2026-02-28 21:31:47 +01:00
|
|
|
const onImageUrl = useInsertImageCallback(editor)
|
|
|
|
|
const { isDragOver } = useImageDrop({ containerRef, onImageUrl, vaultPath })
|
2026-04-15 19:42:25 +02:00
|
|
|
useBlockNoteSideMenuHoverGuard(containerRef)
|
2026-04-08 19:06:21 +02:00
|
|
|
useEditorLinkActivation(containerRef, onNavigateWikilink)
|
refactor: decompose Editor.tsx, NoteList.tsx, Sidebar.tsx — CodeScene hotspots (#126)
* refactor: decompose Editor.tsx into focused subcomponents and hooks
Extract from the monolithic Editor component (cc=35, 213 LoC, score 7.82):
- useDiffMode hook: diff state + toggle/commit-diff handlers
- useEditorFocus hook: new-note focus event listener
- editorSchema: WikiLink spec + BlockNote schema (module-level)
- SingleEditorView: BlockNote editor + suggestion menus
- EditorContent: breadcrumb bar + diff/editor/loading views
- EditorRightPanel: AI chat / Inspector panel switching
- suggestionEnrichment util: shared enrichment logic (eliminates duplication)
Editor.tsx is now 10.0 code health (was 7.82). All 25 existing tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract standalone functions from NoteList and Sidebar to reduce cc
NoteList: extract createNoteStatusResolver and toggleSetMember from
NoteListInner (cc 13→8, score 9.04→9.38).
Sidebar: extract applyCustomization from Sidebar (cc 10→7, score 9.09→10.0).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add tests for useDiffMode, useEditorFocus, and suggestionEnrichment
Cover extracted hook/utility logic: diff toggle/reset, editor focus event
listener with adaptive timing, and suggestion item enrichment pipeline.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: update architecture for editor decomposition and write .claude-done
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-27 15:27:25 +01:00
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
_wikilinkEntriesRef.current = entries
|
|
|
|
|
}, [entries])
|
|
|
|
|
|
2026-04-26 03:01:10 +02:00
|
|
|
useEffect(() => {
|
|
|
|
|
const container = containerRef.current
|
|
|
|
|
if (!container) return
|
|
|
|
|
return observeNativeTextAssistanceDisabled(container)
|
|
|
|
|
}, [])
|
|
|
|
|
|
2026-04-12 11:45:59 +02:00
|
|
|
useSeedBlockNoteTableBridge(editor)
|
|
|
|
|
|
refactor: decompose Editor.tsx, NoteList.tsx, Sidebar.tsx — CodeScene hotspots (#126)
* refactor: decompose Editor.tsx into focused subcomponents and hooks
Extract from the monolithic Editor component (cc=35, 213 LoC, score 7.82):
- useDiffMode hook: diff state + toggle/commit-diff handlers
- useEditorFocus hook: new-note focus event listener
- editorSchema: WikiLink spec + BlockNote schema (module-level)
- SingleEditorView: BlockNote editor + suggestion menus
- EditorContent: breadcrumb bar + diff/editor/loading views
- EditorRightPanel: AI chat / Inspector panel switching
- suggestionEnrichment util: shared enrichment logic (eliminates duplication)
Editor.tsx is now 10.0 code health (was 7.82). All 25 existing tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract standalone functions from NoteList and Sidebar to reduce cc
NoteList: extract createNoteStatusResolver and toggleSetMember from
NoteListInner (cc 13→8, score 9.04→9.38).
Sidebar: extract applyCustomization from Sidebar (cc 10→7, score 9.09→10.0).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add tests for useDiffMode, useEditorFocus, and suggestionEnrichment
Cover extracted hook/utility logic: diff toggle/reset, editor focus event
listener with adaptive timing, and suggestion item enrichment pipeline.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: update architecture for editor decomposition and write .claude-done
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-27 15:27:25 +01:00
|
|
|
const typeEntryMap = useMemo(() => buildTypeEntryMap(entries), [entries])
|
2026-04-20 18:35:50 +02:00
|
|
|
const baseItems = useMemo(() => buildBaseSuggestionItems(entries), [entries])
|
2026-04-28 23:29:09 +02:00
|
|
|
const runEditorAction = useCallback((action: SuggestionAction) => {
|
|
|
|
|
runSuggestionActionSafely({
|
|
|
|
|
action,
|
|
|
|
|
container: containerRef.current,
|
|
|
|
|
editor,
|
|
|
|
|
})
|
|
|
|
|
}, [editor])
|
|
|
|
|
const insertWikilink = useInsertWikilink(editor, runEditorAction)
|
2026-04-20 18:35:50 +02:00
|
|
|
const {
|
|
|
|
|
getWikilinkItems,
|
|
|
|
|
getPersonMentionItems,
|
|
|
|
|
getSlashMenuItems,
|
|
|
|
|
} = useSuggestionMenuItems({
|
|
|
|
|
baseItems,
|
|
|
|
|
editor,
|
|
|
|
|
insertWikilink,
|
2026-04-28 23:29:09 +02:00
|
|
|
runEditorAction,
|
2026-04-20 18:35:50 +02:00
|
|
|
typeEntryMap,
|
|
|
|
|
vaultPath,
|
|
|
|
|
})
|
2026-04-15 22:42:40 +02:00
|
|
|
|
refactor: decompose Editor.tsx, NoteList.tsx, Sidebar.tsx — CodeScene hotspots (#126)
* refactor: decompose Editor.tsx into focused subcomponents and hooks
Extract from the monolithic Editor component (cc=35, 213 LoC, score 7.82):
- useDiffMode hook: diff state + toggle/commit-diff handlers
- useEditorFocus hook: new-note focus event listener
- editorSchema: WikiLink spec + BlockNote schema (module-level)
- SingleEditorView: BlockNote editor + suggestion menus
- EditorContent: breadcrumb bar + diff/editor/loading views
- EditorRightPanel: AI chat / Inspector panel switching
- suggestionEnrichment util: shared enrichment logic (eliminates duplication)
Editor.tsx is now 10.0 code health (was 7.82). All 25 existing tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract standalone functions from NoteList and Sidebar to reduce cc
NoteList: extract createNoteStatusResolver and toggleSetMember from
NoteListInner (cc 13→8, score 9.04→9.38).
Sidebar: extract applyCustomization from Sidebar (cc 10→7, score 9.09→10.0).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add tests for useDiffMode, useEditorFocus, and suggestionEnrichment
Cover extracted hook/utility logic: diff toggle/reset, editor focus event
listener with adaptive timing, and suggestion item enrichment pipeline.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: update architecture for editor decomposition and write .claude-done
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-27 15:27:25 +01:00
|
|
|
return (
|
2026-04-27 18:31:51 +02:00
|
|
|
<div ref={containerRef} className={`editor__blocknote-container${isDragOver ? ' editor__blocknote-container--drag-over' : ''}`} style={cssVars as React.CSSProperties} onClick={handleContainerClick} onCopyCapture={handleCodeBlockCopy}>
|
refactor: decompose Editor.tsx, NoteList.tsx, Sidebar.tsx — CodeScene hotspots (#126)
* refactor: decompose Editor.tsx into focused subcomponents and hooks
Extract from the monolithic Editor component (cc=35, 213 LoC, score 7.82):
- useDiffMode hook: diff state + toggle/commit-diff handlers
- useEditorFocus hook: new-note focus event listener
- editorSchema: WikiLink spec + BlockNote schema (module-level)
- SingleEditorView: BlockNote editor + suggestion menus
- EditorContent: breadcrumb bar + diff/editor/loading views
- EditorRightPanel: AI chat / Inspector panel switching
- suggestionEnrichment util: shared enrichment logic (eliminates duplication)
Editor.tsx is now 10.0 code health (was 7.82). All 25 existing tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract standalone functions from NoteList and Sidebar to reduce cc
NoteList: extract createNoteStatusResolver and toggleSetMember from
NoteListInner (cc 13→8, score 9.04→9.38).
Sidebar: extract applyCustomization from Sidebar (cc 10→7, score 9.09→10.0).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add tests for useDiffMode, useEditorFocus, and suggestionEnrichment
Cover extracted hook/utility logic: diff toggle/reset, editor focus event
listener with adaptive timing, and suggestion item enrichment pipeline.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: update architecture for editor decomposition and write .claude-done
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-27 15:27:25 +01:00
|
|
|
{isDragOver && (
|
|
|
|
|
<div className="editor__drop-overlay">
|
|
|
|
|
<div className="editor__drop-overlay-label">Drop image here</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
2026-04-13 22:45:25 +02:00
|
|
|
<SharedContextBlockNoteView
|
refactor: decompose Editor.tsx, NoteList.tsx, Sidebar.tsx — CodeScene hotspots (#126)
* refactor: decompose Editor.tsx into focused subcomponents and hooks
Extract from the monolithic Editor component (cc=35, 213 LoC, score 7.82):
- useDiffMode hook: diff state + toggle/commit-diff handlers
- useEditorFocus hook: new-note focus event listener
- editorSchema: WikiLink spec + BlockNote schema (module-level)
- SingleEditorView: BlockNote editor + suggestion menus
- EditorContent: breadcrumb bar + diff/editor/loading views
- EditorRightPanel: AI chat / Inspector panel switching
- suggestionEnrichment util: shared enrichment logic (eliminates duplication)
Editor.tsx is now 10.0 code health (was 7.82). All 25 existing tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract standalone functions from NoteList and Sidebar to reduce cc
NoteList: extract createNoteStatusResolver and toggleSetMember from
NoteListInner (cc 13→8, score 9.04→9.38).
Sidebar: extract applyCustomization from Sidebar (cc 10→7, score 9.09→10.0).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add tests for useDiffMode, useEditorFocus, and suggestionEnrichment
Cover extracted hook/utility logic: diff toggle/reset, editor focus event
listener with adaptive timing, and suggestion item enrichment pipeline.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: update architecture for editor decomposition and write .claude-done
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-27 15:27:25 +01:00
|
|
|
editor={editor}
|
2026-04-24 22:26:07 +02:00
|
|
|
theme={themeMode}
|
2026-04-24 02:50:12 +02:00
|
|
|
onChange={handleEditorChange}
|
2026-03-03 01:37:25 +01:00
|
|
|
editable={editable}
|
2026-04-15 22:42:40 +02:00
|
|
|
formattingToolbar={false}
|
2026-04-24 23:25:22 +02:00
|
|
|
linkToolbar={false}
|
2026-04-15 22:42:40 +02:00
|
|
|
slashMenu={false}
|
2026-04-17 17:48:12 +02:00
|
|
|
sideMenu={false}
|
refactor: decompose Editor.tsx, NoteList.tsx, Sidebar.tsx — CodeScene hotspots (#126)
* refactor: decompose Editor.tsx into focused subcomponents and hooks
Extract from the monolithic Editor component (cc=35, 213 LoC, score 7.82):
- useDiffMode hook: diff state + toggle/commit-diff handlers
- useEditorFocus hook: new-note focus event listener
- editorSchema: WikiLink spec + BlockNote schema (module-level)
- SingleEditorView: BlockNote editor + suggestion menus
- EditorContent: breadcrumb bar + diff/editor/loading views
- EditorRightPanel: AI chat / Inspector panel switching
- suggestionEnrichment util: shared enrichment logic (eliminates duplication)
Editor.tsx is now 10.0 code health (was 7.82). All 25 existing tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract standalone functions from NoteList and Sidebar to reduce cc
NoteList: extract createNoteStatusResolver and toggleSetMember from
NoteListInner (cc 13→8, score 9.04→9.38).
Sidebar: extract applyCustomization from Sidebar (cc 10→7, score 9.09→10.0).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add tests for useDiffMode, useEditorFocus, and suggestionEnrichment
Cover extracted hook/utility logic: diff toggle/reset, editor focus event
listener with adaptive timing, and suggestion item enrichment pipeline.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: update architecture for editor decomposition and write .claude-done
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-27 15:27:25 +01:00
|
|
|
>
|
2026-04-17 17:48:12 +02:00
|
|
|
<SideMenuController sideMenu={TolariaSideMenu} />
|
2026-04-16 11:24:37 +02:00
|
|
|
<TolariaFormattingToolbarController
|
2026-04-15 22:42:40 +02:00
|
|
|
formattingToolbar={TolariaFormattingToolbar}
|
|
|
|
|
floatingUIOptions={{
|
|
|
|
|
elementProps: {
|
2026-04-24 23:25:22 +02:00
|
|
|
onMouseDownCapture: handleToolbarMouseDownCapture,
|
|
|
|
|
},
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
<LinkToolbarController
|
|
|
|
|
linkToolbar={TolariaLinkToolbar}
|
|
|
|
|
floatingUIOptions={{
|
|
|
|
|
elementProps: {
|
|
|
|
|
onMouseDownCapture: handleToolbarMouseDownCapture,
|
2026-04-15 22:42:40 +02:00
|
|
|
},
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
<SuggestionMenuController
|
|
|
|
|
triggerCharacter="/"
|
|
|
|
|
getItems={getSlashMenuItems}
|
|
|
|
|
/>
|
refactor: decompose Editor.tsx, NoteList.tsx, Sidebar.tsx — CodeScene hotspots (#126)
* refactor: decompose Editor.tsx into focused subcomponents and hooks
Extract from the monolithic Editor component (cc=35, 213 LoC, score 7.82):
- useDiffMode hook: diff state + toggle/commit-diff handlers
- useEditorFocus hook: new-note focus event listener
- editorSchema: WikiLink spec + BlockNote schema (module-level)
- SingleEditorView: BlockNote editor + suggestion menus
- EditorContent: breadcrumb bar + diff/editor/loading views
- EditorRightPanel: AI chat / Inspector panel switching
- suggestionEnrichment util: shared enrichment logic (eliminates duplication)
Editor.tsx is now 10.0 code health (was 7.82). All 25 existing tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract standalone functions from NoteList and Sidebar to reduce cc
NoteList: extract createNoteStatusResolver and toggleSetMember from
NoteListInner (cc 13→8, score 9.04→9.38).
Sidebar: extract applyCustomization from Sidebar (cc 10→7, score 9.09→10.0).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add tests for useDiffMode, useEditorFocus, and suggestionEnrichment
Cover extracted hook/utility logic: diff toggle/reset, editor focus event
listener with adaptive timing, and suggestion item enrichment pipeline.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: update architecture for editor decomposition and write .claude-done
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-27 15:27:25 +01:00
|
|
|
<SuggestionMenuController
|
|
|
|
|
triggerCharacter="[["
|
|
|
|
|
getItems={getWikilinkItems}
|
|
|
|
|
suggestionMenuComponent={WikilinkSuggestionMenu}
|
2026-04-28 23:29:09 +02:00
|
|
|
onItemClick={(item: WikilinkSuggestionItem) => runEditorAction(item.onItemClick)}
|
refactor: decompose Editor.tsx, NoteList.tsx, Sidebar.tsx — CodeScene hotspots (#126)
* refactor: decompose Editor.tsx into focused subcomponents and hooks
Extract from the monolithic Editor component (cc=35, 213 LoC, score 7.82):
- useDiffMode hook: diff state + toggle/commit-diff handlers
- useEditorFocus hook: new-note focus event listener
- editorSchema: WikiLink spec + BlockNote schema (module-level)
- SingleEditorView: BlockNote editor + suggestion menus
- EditorContent: breadcrumb bar + diff/editor/loading views
- EditorRightPanel: AI chat / Inspector panel switching
- suggestionEnrichment util: shared enrichment logic (eliminates duplication)
Editor.tsx is now 10.0 code health (was 7.82). All 25 existing tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract standalone functions from NoteList and Sidebar to reduce cc
NoteList: extract createNoteStatusResolver and toggleSetMember from
NoteListInner (cc 13→8, score 9.04→9.38).
Sidebar: extract applyCustomization from Sidebar (cc 10→7, score 9.09→10.0).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add tests for useDiffMode, useEditorFocus, and suggestionEnrichment
Cover extracted hook/utility logic: diff toggle/reset, editor focus event
listener with adaptive timing, and suggestion item enrichment pipeline.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: update architecture for editor decomposition and write .claude-done
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-27 15:27:25 +01:00
|
|
|
/>
|
|
|
|
|
<SuggestionMenuController
|
|
|
|
|
triggerCharacter="@"
|
|
|
|
|
getItems={getPersonMentionItems}
|
|
|
|
|
suggestionMenuComponent={WikilinkSuggestionMenu}
|
2026-04-28 23:29:09 +02:00
|
|
|
onItemClick={(item: WikilinkSuggestionItem) => runEditorAction(item.onItemClick)}
|
refactor: decompose Editor.tsx, NoteList.tsx, Sidebar.tsx — CodeScene hotspots (#126)
* refactor: decompose Editor.tsx into focused subcomponents and hooks
Extract from the monolithic Editor component (cc=35, 213 LoC, score 7.82):
- useDiffMode hook: diff state + toggle/commit-diff handlers
- useEditorFocus hook: new-note focus event listener
- editorSchema: WikiLink spec + BlockNote schema (module-level)
- SingleEditorView: BlockNote editor + suggestion menus
- EditorContent: breadcrumb bar + diff/editor/loading views
- EditorRightPanel: AI chat / Inspector panel switching
- suggestionEnrichment util: shared enrichment logic (eliminates duplication)
Editor.tsx is now 10.0 code health (was 7.82). All 25 existing tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract standalone functions from NoteList and Sidebar to reduce cc
NoteList: extract createNoteStatusResolver and toggleSetMember from
NoteListInner (cc 13→8, score 9.04→9.38).
Sidebar: extract applyCustomization from Sidebar (cc 10→7, score 9.09→10.0).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add tests for useDiffMode, useEditorFocus, and suggestionEnrichment
Cover extracted hook/utility logic: diff toggle/reset, editor focus event
listener with adaptive timing, and suggestion item enrichment pipeline.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: update architecture for editor decomposition and write .claude-done
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-27 15:27:25 +01:00
|
|
|
/>
|
2026-04-13 22:45:25 +02:00
|
|
|
</SharedContextBlockNoteView>
|
refactor: decompose Editor.tsx, NoteList.tsx, Sidebar.tsx — CodeScene hotspots (#126)
* refactor: decompose Editor.tsx into focused subcomponents and hooks
Extract from the monolithic Editor component (cc=35, 213 LoC, score 7.82):
- useDiffMode hook: diff state + toggle/commit-diff handlers
- useEditorFocus hook: new-note focus event listener
- editorSchema: WikiLink spec + BlockNote schema (module-level)
- SingleEditorView: BlockNote editor + suggestion menus
- EditorContent: breadcrumb bar + diff/editor/loading views
- EditorRightPanel: AI chat / Inspector panel switching
- suggestionEnrichment util: shared enrichment logic (eliminates duplication)
Editor.tsx is now 10.0 code health (was 7.82). All 25 existing tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract standalone functions from NoteList and Sidebar to reduce cc
NoteList: extract createNoteStatusResolver and toggleSetMember from
NoteListInner (cc 13→8, score 9.04→9.38).
Sidebar: extract applyCustomization from Sidebar (cc 10→7, score 9.09→10.0).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add tests for useDiffMode, useEditorFocus, and suggestionEnrichment
Cover extracted hook/utility logic: diff toggle/reset, editor focus event
listener with adaptive timing, and suggestion item enrichment pipeline.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: update architecture for editor decomposition and write .claude-done
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-27 15:27:25 +01:00
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|