@llui/markdown-editor 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/plugins/block-drag.d.ts +1 -0
- package/dist/plugins/block-drag.d.ts.map +1 -1
- package/dist/plugins/block-drag.js +251 -28
- package/dist/plugins/block-drag.js.map +1 -1
- package/dist/plugins/wikilink.d.ts +19 -0
- package/dist/plugins/wikilink.d.ts.map +1 -1
- package/dist/plugins/wikilink.js +235 -10
- package/dist/plugins/wikilink.js.map +1 -1
- package/dist/styles/block-drag.css +55 -0
- package/dist/styles/editor.css +48 -0
- package/package.json +6 -6
- package/src/plugins/block-drag.ts +291 -28
- package/src/plugins/wikilink.ts +301 -11
- package/src/styles/block-drag.css +55 -0
- package/src/styles/editor.css +48 -0
package/src/plugins/wikilink.ts
CHANGED
|
@@ -65,12 +65,18 @@
|
|
|
65
65
|
|
|
66
66
|
import {
|
|
67
67
|
$applyNodeReplacement,
|
|
68
|
+
$createTextNode,
|
|
68
69
|
$getNearestNodeFromDOMNode,
|
|
69
70
|
$getSelection,
|
|
70
71
|
$isRangeSelection,
|
|
72
|
+
$isTextNode,
|
|
71
73
|
CLICK_COMMAND,
|
|
74
|
+
COMMAND_PRIORITY_HIGH,
|
|
72
75
|
COMMAND_PRIORITY_LOW,
|
|
76
|
+
KEY_ARROW_DOWN_COMMAND,
|
|
77
|
+
KEY_ARROW_UP_COMMAND,
|
|
73
78
|
KEY_ENTER_COMMAND,
|
|
79
|
+
KEY_ESCAPE_COMMAND,
|
|
74
80
|
TextNode,
|
|
75
81
|
type EditorConfig,
|
|
76
82
|
type LexicalEditor,
|
|
@@ -82,8 +88,10 @@ import {
|
|
|
82
88
|
} from 'lexical'
|
|
83
89
|
import type { TextMatchTransformer } from '@lexical/markdown'
|
|
84
90
|
import { mergeRegister } from '@lexical/utils'
|
|
91
|
+
import { div, each, text, type Signal } from '@llui/dom'
|
|
85
92
|
import { setTransformerPrecedence } from '../transformers/registry.js'
|
|
86
93
|
import { definePluginUI } from './ui.js'
|
|
94
|
+
import { OVERLAY_Z, overlayRoot } from './overlay.js'
|
|
87
95
|
import type { CommandItem, MarkdownPlugin } from './types.js'
|
|
88
96
|
|
|
89
97
|
const PLUGIN = 'wikilink'
|
|
@@ -420,14 +428,89 @@ setTransformerPrecedence(WIKILINK_TRANSFORMER, -10)
|
|
|
420
428
|
|
|
421
429
|
// ── Click → host notification ────────────────────────────────────────────────
|
|
422
430
|
|
|
431
|
+
/** A document the host offers as a link target while the user types `[[…`. */
|
|
432
|
+
export interface DocCandidate {
|
|
433
|
+
/** The link target written into the document (`[[target]]`). */
|
|
434
|
+
readonly target: string
|
|
435
|
+
/** Human-facing title shown in the results list (defaults to `target`). */
|
|
436
|
+
readonly title?: string
|
|
437
|
+
/** A short one-line snippet shown under the title. */
|
|
438
|
+
readonly snippet?: string
|
|
439
|
+
/** A longer content excerpt shown in the reference/preview pane. */
|
|
440
|
+
readonly preview?: string
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/** A results row with the active flag the view renders from. */
|
|
444
|
+
interface DocRow {
|
|
445
|
+
target: string
|
|
446
|
+
title: string
|
|
447
|
+
snippet: string
|
|
448
|
+
preview: string
|
|
449
|
+
active: boolean
|
|
450
|
+
}
|
|
451
|
+
|
|
423
452
|
interface WikiLinkState {
|
|
424
453
|
/** The most recently activated link (JSON-serializable, replay-safe). */
|
|
425
454
|
last: WikiLink | null
|
|
455
|
+
/** Document-search panel: open while typing `[[…` with results to show. */
|
|
456
|
+
searchOpen: boolean
|
|
457
|
+
/** The current `[[` query text (what follows `[[`, before the caret). */
|
|
458
|
+
searchQuery: string
|
|
459
|
+
searchItems: DocRow[]
|
|
460
|
+
searchIndex: number
|
|
461
|
+
searchX: number
|
|
462
|
+
searchY: number
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
type WikiLinkMsg =
|
|
466
|
+
| { type: 'activate'; target: string; alias: string | null }
|
|
467
|
+
| { type: 'searchShow'; query: string; items: readonly DocCandidate[]; x: number; y: number }
|
|
468
|
+
| { type: 'searchHide' }
|
|
469
|
+
| { type: 'searchMove'; delta: number }
|
|
470
|
+
| { type: 'searchChoose' }
|
|
471
|
+
| { type: 'searchClick'; index: number }
|
|
472
|
+
| { type: 'searchHover'; index: number }
|
|
473
|
+
|
|
474
|
+
type WikiLinkEffect =
|
|
475
|
+
| { type: 'navigate'; link: WikiLink }
|
|
476
|
+
| { type: 'insert'; target: string; query: string }
|
|
477
|
+
|
|
478
|
+
const MAX_RESULTS = 8
|
|
479
|
+
/** Debounce before firing the host's (possibly async) `search` for a query. */
|
|
480
|
+
const SEARCH_DEBOUNCE_MS = 120
|
|
481
|
+
|
|
482
|
+
function toRows(items: readonly DocCandidate[], index: number): DocRow[] {
|
|
483
|
+
return items.map((c, i) => ({
|
|
484
|
+
target: c.target,
|
|
485
|
+
title: c.title ?? c.target,
|
|
486
|
+
snippet: c.snippet ?? '',
|
|
487
|
+
preview: c.preview ?? '',
|
|
488
|
+
active: i === index,
|
|
489
|
+
}))
|
|
426
490
|
}
|
|
427
491
|
|
|
428
|
-
|
|
492
|
+
/** The `[[` query immediately before a collapsed caret, or `null` when the caret
|
|
493
|
+
* is not inside an open `[[…` (no `[[`, or a `]`/`[`/newline already closed it). */
|
|
494
|
+
function $readWikiQuery(): string | null {
|
|
495
|
+
const selection = $getSelection()
|
|
496
|
+
if (!$isRangeSelection(selection) || !selection.isCollapsed()) return null
|
|
497
|
+
const node = selection.anchor.getNode()
|
|
498
|
+
if (!$isTextNode(node)) return null
|
|
499
|
+
const before = node.getTextContent().slice(0, selection.anchor.offset)
|
|
500
|
+
const match = before.match(/\[\[([^[\]\n|]*)$/)
|
|
501
|
+
return match ? (match[1] ?? '') : null
|
|
502
|
+
}
|
|
429
503
|
|
|
430
|
-
|
|
504
|
+
/** Viewport point just below the caret, for anchoring the panel. */
|
|
505
|
+
function caretXY(): { x: number; y: number } {
|
|
506
|
+
if (typeof window === 'undefined') return { x: 0, y: 0 }
|
|
507
|
+
const sel = window.getSelection()
|
|
508
|
+
if (sel && sel.rangeCount > 0) {
|
|
509
|
+
const rect = sel.getRangeAt(0).getBoundingClientRect()
|
|
510
|
+
return { x: rect.left, y: rect.bottom + 4 }
|
|
511
|
+
}
|
|
512
|
+
return { x: 0, y: 0 }
|
|
513
|
+
}
|
|
431
514
|
|
|
432
515
|
/** Resolve the wikilink a click landed on, if any. Must run in an editor scope. */
|
|
433
516
|
function $wikiLinkFromEvent(event: MouseEvent): WikiLinkNode | null {
|
|
@@ -473,6 +556,14 @@ export interface WikiLinkPluginOptions {
|
|
|
473
556
|
onNavigate?: (link: WikiLink) => void
|
|
474
557
|
/** Text used as the target when the insert command runs with no selection. */
|
|
475
558
|
placeholderTarget?: string
|
|
559
|
+
/**
|
|
560
|
+
* Document-search seam: as the user types `[[query`, resolve matching
|
|
561
|
+
* documents to offer as link targets, with an optional content preview shown in
|
|
562
|
+
* the panel's reference pane. Sync or async (async is debounced; a stale
|
|
563
|
+
* response for a superseded query is dropped). When omitted, the panel never
|
|
564
|
+
* opens and `[[target]]` still works by typing the closing `]]`.
|
|
565
|
+
*/
|
|
566
|
+
search?: (query: string) => readonly DocCandidate[] | Promise<readonly DocCandidate[]>
|
|
476
567
|
}
|
|
477
568
|
|
|
478
569
|
export function wikilinkPlugin(opts: WikiLinkPluginOptions = {}): MarkdownPlugin {
|
|
@@ -480,10 +571,10 @@ export function wikilinkPlugin(opts: WikiLinkPluginOptions = {}): MarkdownPlugin
|
|
|
480
571
|
|
|
481
572
|
const item: CommandItem = {
|
|
482
573
|
id: PLUGIN,
|
|
483
|
-
label: '
|
|
574
|
+
label: 'Document link',
|
|
484
575
|
icon: 'wikilink',
|
|
485
576
|
group: 'inline',
|
|
486
|
-
keywords: ['wiki', 'wikilink', 'backlink', 'reference', '[['],
|
|
577
|
+
keywords: ['document', 'doc', 'link', 'wiki', 'wikilink', 'backlink', 'reference', '[['],
|
|
487
578
|
run: (editor) =>
|
|
488
579
|
editor.update(() => {
|
|
489
580
|
const selection = $getSelection()
|
|
@@ -508,12 +599,13 @@ export function wikilinkPlugin(opts: WikiLinkPluginOptions = {}): MarkdownPlugin
|
|
|
508
599
|
transformers: [WIKILINK_TRANSFORMER],
|
|
509
600
|
items: [item],
|
|
510
601
|
register: (editor, ctx) => {
|
|
602
|
+
const emit = (msg: WikiLinkMsg): void => ctx.emit({ type: 'plugin', name: PLUGIN, msg })
|
|
511
603
|
const activate = (node: WikiLinkNode): true => {
|
|
512
604
|
const { target, alias } = node.getLink()
|
|
513
|
-
|
|
605
|
+
emit({ type: 'activate', target, alias })
|
|
514
606
|
return true
|
|
515
607
|
}
|
|
516
|
-
|
|
608
|
+
const navigation = mergeRegister(
|
|
517
609
|
editor.registerCommand(
|
|
518
610
|
CLICK_COMMAND,
|
|
519
611
|
(event: MouseEvent) => {
|
|
@@ -540,14 +632,212 @@ export function wikilinkPlugin(opts: WikiLinkPluginOptions = {}): MarkdownPlugin
|
|
|
540
632
|
COMMAND_PRIORITY_LOW,
|
|
541
633
|
),
|
|
542
634
|
)
|
|
635
|
+
|
|
636
|
+
// Document-search typeahead is opt-in: with no `search` seam the panel never
|
|
637
|
+
// opens and `[[target]]` still works by typing the closing `]]`.
|
|
638
|
+
const search = opts.search
|
|
639
|
+
if (search === undefined) return navigation
|
|
640
|
+
|
|
641
|
+
let seq = 0
|
|
642
|
+
let timer: ReturnType<typeof setTimeout> | null = null
|
|
643
|
+
const clearTimer = (): void => {
|
|
644
|
+
if (timer !== null) {
|
|
645
|
+
clearTimeout(timer)
|
|
646
|
+
timer = null
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
const activeQuery = (): string | null => editor.getEditorState().read(() => $readWikiQuery())
|
|
650
|
+
const isActive = (): boolean => activeQuery() !== null
|
|
651
|
+
|
|
652
|
+
const refresh = (): void => {
|
|
653
|
+
const query = activeQuery()
|
|
654
|
+
if (query === null) {
|
|
655
|
+
clearTimer()
|
|
656
|
+
emit({ type: 'searchHide' })
|
|
657
|
+
return
|
|
658
|
+
}
|
|
659
|
+
clearTimer()
|
|
660
|
+
const mine = ++seq
|
|
661
|
+
timer = setTimeout(() => {
|
|
662
|
+
void Promise.resolve(search(query))
|
|
663
|
+
.then((results) => {
|
|
664
|
+
// Drop a superseded response (a later keystroke won) or one whose
|
|
665
|
+
// query the caret has since left/changed.
|
|
666
|
+
if (mine !== seq || activeQuery() !== query) return
|
|
667
|
+
const { x, y } = caretXY()
|
|
668
|
+
emit({ type: 'searchShow', query, items: results.slice(0, MAX_RESULTS), x, y })
|
|
669
|
+
})
|
|
670
|
+
.catch(() => {
|
|
671
|
+
/* a failed search simply shows nothing */
|
|
672
|
+
})
|
|
673
|
+
}, SEARCH_DEBOUNCE_MS)
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
const nav = (delta: number, e: KeyboardEvent | null): boolean => {
|
|
677
|
+
if (!isActive()) return false
|
|
678
|
+
e?.preventDefault()
|
|
679
|
+
emit({ type: 'searchMove', delta })
|
|
680
|
+
return true
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
return mergeRegister(
|
|
684
|
+
navigation,
|
|
685
|
+
editor.registerUpdateListener(() => refresh()),
|
|
686
|
+
editor.registerCommand(KEY_ARROW_DOWN_COMMAND, (e) => nav(1, e), COMMAND_PRIORITY_HIGH),
|
|
687
|
+
editor.registerCommand(KEY_ARROW_UP_COMMAND, (e) => nav(-1, e), COMMAND_PRIORITY_HIGH),
|
|
688
|
+
editor.registerCommand(
|
|
689
|
+
KEY_ENTER_COMMAND,
|
|
690
|
+
(e) => {
|
|
691
|
+
if (!isActive()) return false
|
|
692
|
+
e?.preventDefault()
|
|
693
|
+
emit({ type: 'searchChoose' })
|
|
694
|
+
return true
|
|
695
|
+
},
|
|
696
|
+
COMMAND_PRIORITY_HIGH,
|
|
697
|
+
),
|
|
698
|
+
editor.registerCommand(
|
|
699
|
+
KEY_ESCAPE_COMMAND,
|
|
700
|
+
() => {
|
|
701
|
+
if (!isActive()) return false
|
|
702
|
+
emit({ type: 'searchHide' })
|
|
703
|
+
return true
|
|
704
|
+
},
|
|
705
|
+
COMMAND_PRIORITY_HIGH,
|
|
706
|
+
),
|
|
707
|
+
clearTimer,
|
|
708
|
+
)
|
|
543
709
|
},
|
|
544
710
|
ui: definePluginUI<WikiLinkState, WikiLinkMsg, WikiLinkEffect>({
|
|
545
|
-
init: () => ({
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
711
|
+
init: () => ({
|
|
712
|
+
last: null,
|
|
713
|
+
searchOpen: false,
|
|
714
|
+
searchQuery: '',
|
|
715
|
+
searchItems: [],
|
|
716
|
+
searchIndex: 0,
|
|
717
|
+
searchX: 0,
|
|
718
|
+
searchY: 0,
|
|
719
|
+
}),
|
|
720
|
+
update: (state, msg) => {
|
|
721
|
+
switch (msg.type) {
|
|
722
|
+
case 'activate': {
|
|
723
|
+
const link: WikiLink = { target: msg.target, alias: msg.alias }
|
|
724
|
+
return [{ ...state, last: link }, [{ type: 'navigate', link }]]
|
|
725
|
+
}
|
|
726
|
+
case 'searchShow':
|
|
727
|
+
return {
|
|
728
|
+
...state,
|
|
729
|
+
searchOpen: msg.items.length > 0,
|
|
730
|
+
searchQuery: msg.query,
|
|
731
|
+
searchItems: toRows(msg.items, 0),
|
|
732
|
+
searchIndex: 0,
|
|
733
|
+
searchX: msg.x,
|
|
734
|
+
searchY: msg.y,
|
|
735
|
+
}
|
|
736
|
+
case 'searchHide':
|
|
737
|
+
return state.searchOpen ? { ...state, searchOpen: false } : state
|
|
738
|
+
case 'searchMove': {
|
|
739
|
+
if (!state.searchOpen || state.searchItems.length === 0) return state
|
|
740
|
+
const i =
|
|
741
|
+
(state.searchIndex + msg.delta + state.searchItems.length) % state.searchItems.length
|
|
742
|
+
return {
|
|
743
|
+
...state,
|
|
744
|
+
searchIndex: i,
|
|
745
|
+
searchItems: state.searchItems.map((r, idx) => ({ ...r, active: idx === i })),
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
case 'searchHover': {
|
|
749
|
+
if (msg.index < 0 || msg.index >= state.searchItems.length) return state
|
|
750
|
+
return {
|
|
751
|
+
...state,
|
|
752
|
+
searchIndex: msg.index,
|
|
753
|
+
searchItems: state.searchItems.map((r, idx) => ({ ...r, active: idx === msg.index })),
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
case 'searchChoose': {
|
|
757
|
+
const row = state.searchItems[state.searchIndex]
|
|
758
|
+
if (!state.searchOpen || !row) return { ...state, searchOpen: false }
|
|
759
|
+
return [
|
|
760
|
+
{ ...state, searchOpen: false },
|
|
761
|
+
[{ type: 'insert', target: row.target, query: state.searchQuery }],
|
|
762
|
+
]
|
|
763
|
+
}
|
|
764
|
+
case 'searchClick': {
|
|
765
|
+
const row = state.searchItems[msg.index]
|
|
766
|
+
if (!row) return state
|
|
767
|
+
return [
|
|
768
|
+
{ ...state, searchOpen: false },
|
|
769
|
+
[{ type: 'insert', target: row.target, query: state.searchQuery }],
|
|
770
|
+
]
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
},
|
|
774
|
+
onEffect: (effect, ctx) => {
|
|
775
|
+
if (effect.type === 'navigate') {
|
|
776
|
+
opts.onNavigate?.(effect.link)
|
|
777
|
+
return
|
|
778
|
+
}
|
|
779
|
+
// insert: replace the typed `[[query` with a wikilink node + a space.
|
|
780
|
+
const editor = ctx.editor()
|
|
781
|
+
if (!editor) return
|
|
782
|
+
editor.update(() => {
|
|
783
|
+
const selection = $getSelection()
|
|
784
|
+
if (!$isRangeSelection(selection) || !selection.isCollapsed()) return
|
|
785
|
+
const node = selection.anchor.getNode()
|
|
786
|
+
if (!$isTextNode(node)) return
|
|
787
|
+
const offset = selection.anchor.offset
|
|
788
|
+
// Remove the `[[` (2 chars) plus the query text.
|
|
789
|
+
const start = offset - effect.query.length - 2
|
|
790
|
+
if (start < 0) return
|
|
791
|
+
node.spliceText(start, effect.query.length + 2, '', true)
|
|
792
|
+
const sel = $getSelection()
|
|
793
|
+
if (!$isRangeSelection(sel)) return
|
|
794
|
+
sel.insertNodes([$createWikiLinkNode(effect.target, null), $createTextNode(' ')])
|
|
795
|
+
})
|
|
549
796
|
},
|
|
550
|
-
|
|
797
|
+
view: ({ state, send }) =>
|
|
798
|
+
overlayRoot({
|
|
799
|
+
open: state.at('searchOpen'),
|
|
800
|
+
x: state.at('searchX'),
|
|
801
|
+
y: state.at('searchY'),
|
|
802
|
+
zIndex: OVERLAY_Z.typeahead,
|
|
803
|
+
attrs: { 'data-scope': 'md-wikilink', 'data-part': 'panel-root' },
|
|
804
|
+
children: () => [
|
|
805
|
+
div({ 'data-scope': 'md-wikilink', 'data-part': 'panel' }, [
|
|
806
|
+
div({ 'data-scope': 'md-wikilink', 'data-part': 'results', role: 'listbox' }, [
|
|
807
|
+
each(state.at('searchItems') as Signal<DocRow[]>, {
|
|
808
|
+
key: (r) => r.target,
|
|
809
|
+
render: (item, index) => [
|
|
810
|
+
div(
|
|
811
|
+
{
|
|
812
|
+
'data-scope': 'md-wikilink',
|
|
813
|
+
'data-part': 'result',
|
|
814
|
+
role: 'option',
|
|
815
|
+
'data-active': item.map((r) => (r.active ? '' : undefined)),
|
|
816
|
+
onMouseDown: (e: MouseEvent) => {
|
|
817
|
+
e.preventDefault()
|
|
818
|
+
send({ type: 'searchClick', index: index.peek() })
|
|
819
|
+
},
|
|
820
|
+
onMouseEnter: () => send({ type: 'searchHover', index: index.peek() }),
|
|
821
|
+
},
|
|
822
|
+
[
|
|
823
|
+
div({ 'data-scope': 'md-wikilink', 'data-part': 'result-title' }, [
|
|
824
|
+
text(item.map((r) => r.title)),
|
|
825
|
+
]),
|
|
826
|
+
div({ 'data-scope': 'md-wikilink', 'data-part': 'result-snippet' }, [
|
|
827
|
+
text(item.map((r) => r.snippet)),
|
|
828
|
+
]),
|
|
829
|
+
],
|
|
830
|
+
),
|
|
831
|
+
],
|
|
832
|
+
}),
|
|
833
|
+
]),
|
|
834
|
+
// The reference pane: the active candidate's content preview.
|
|
835
|
+
div({ 'data-scope': 'md-wikilink', 'data-part': 'preview' }, [
|
|
836
|
+
text(state.map((s) => s.searchItems[s.searchIndex]?.preview ?? '')),
|
|
837
|
+
]),
|
|
838
|
+
]),
|
|
839
|
+
],
|
|
840
|
+
}),
|
|
551
841
|
}),
|
|
552
842
|
}
|
|
553
843
|
}
|
|
@@ -61,3 +61,58 @@
|
|
|
61
61
|
background-color 120ms ease;
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
|
+
|
|
65
|
+
/* Block-actions menu (grip click / right-click). Positioned inline by the plugin;
|
|
66
|
+
* appearance only here. */
|
|
67
|
+
[data-scope='md-block-drag'][data-part='menu'] {
|
|
68
|
+
min-width: 160px;
|
|
69
|
+
padding: 4px;
|
|
70
|
+
border: 1px solid var(--md-border, rgba(127, 127, 127, 0.2));
|
|
71
|
+
border-radius: var(--md-radius, 8px);
|
|
72
|
+
background: var(--md-surface, #fff);
|
|
73
|
+
color: var(--md-text, #1f2328);
|
|
74
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
|
|
75
|
+
font-size: 13px;
|
|
76
|
+
display: flex;
|
|
77
|
+
flex-direction: column;
|
|
78
|
+
gap: 1px;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
[data-scope='md-block-drag'][data-part='menu-label'] {
|
|
82
|
+
padding: 4px 8px 2px;
|
|
83
|
+
font-size: 11px;
|
|
84
|
+
font-weight: 600;
|
|
85
|
+
text-transform: uppercase;
|
|
86
|
+
letter-spacing: 0.04em;
|
|
87
|
+
color: var(--md-muted, #9aa0a6);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
[data-scope='md-block-drag'][data-part='menu-item'] {
|
|
91
|
+
display: flex;
|
|
92
|
+
align-items: center;
|
|
93
|
+
width: 100%;
|
|
94
|
+
padding: 5px 8px;
|
|
95
|
+
border: 0;
|
|
96
|
+
border-radius: 5px;
|
|
97
|
+
background: transparent;
|
|
98
|
+
color: inherit;
|
|
99
|
+
font: inherit;
|
|
100
|
+
text-align: left;
|
|
101
|
+
cursor: pointer;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
[data-scope='md-block-drag'][data-part='menu-item']:hover,
|
|
105
|
+
[data-scope='md-block-drag'][data-part='menu-item']:focus-visible {
|
|
106
|
+
background: var(--md-hover, rgba(127, 127, 127, 0.14));
|
|
107
|
+
outline: none;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
[data-scope='md-block-drag'][data-part='menu-item']:focus-visible {
|
|
111
|
+
box-shadow: inset 0 0 0 2px var(--md-accent, #3b82f6);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
[data-scope='md-block-drag'][data-part='menu-sep'] {
|
|
115
|
+
height: 1px;
|
|
116
|
+
margin: 4px 0;
|
|
117
|
+
background: var(--md-border, rgba(127, 127, 127, 0.2));
|
|
118
|
+
}
|
package/src/styles/editor.css
CHANGED
|
@@ -485,6 +485,54 @@
|
|
|
485
485
|
background: var(--md-primary-soft);
|
|
486
486
|
}
|
|
487
487
|
|
|
488
|
+
/* ── Document-link (wikilink) search + reference panel ────────────────── */
|
|
489
|
+
[data-scope='md-wikilink'][data-part='panel'] {
|
|
490
|
+
display: flex;
|
|
491
|
+
min-width: 22rem;
|
|
492
|
+
max-width: 34rem;
|
|
493
|
+
background: var(--md-surface);
|
|
494
|
+
border: 1px solid var(--md-border);
|
|
495
|
+
border-radius: 8px;
|
|
496
|
+
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.5);
|
|
497
|
+
overflow: hidden;
|
|
498
|
+
}
|
|
499
|
+
[data-scope='md-wikilink'][data-part='results'] {
|
|
500
|
+
flex: 0 0 14rem;
|
|
501
|
+
max-height: 18rem;
|
|
502
|
+
overflow: auto;
|
|
503
|
+
padding: 0.25rem;
|
|
504
|
+
border-right: 1px solid var(--md-border);
|
|
505
|
+
}
|
|
506
|
+
[data-scope='md-wikilink'][data-part='result'] {
|
|
507
|
+
padding: 0.4rem 0.6rem;
|
|
508
|
+
border-radius: 5px;
|
|
509
|
+
cursor: pointer;
|
|
510
|
+
}
|
|
511
|
+
[data-scope='md-wikilink'][data-part='result'][data-active] {
|
|
512
|
+
background: var(--md-primary-soft);
|
|
513
|
+
}
|
|
514
|
+
[data-scope='md-wikilink'][data-part='result-title'] {
|
|
515
|
+
font-size: 0.88rem;
|
|
516
|
+
color: var(--md-text);
|
|
517
|
+
}
|
|
518
|
+
[data-scope='md-wikilink'][data-part='result-snippet'] {
|
|
519
|
+
font-size: 0.78rem;
|
|
520
|
+
color: var(--md-muted);
|
|
521
|
+
white-space: nowrap;
|
|
522
|
+
overflow: hidden;
|
|
523
|
+
text-overflow: ellipsis;
|
|
524
|
+
}
|
|
525
|
+
[data-scope='md-wikilink'][data-part='preview'] {
|
|
526
|
+
flex: 1 1 auto;
|
|
527
|
+
max-height: 18rem;
|
|
528
|
+
overflow: auto;
|
|
529
|
+
padding: 0.6rem 0.75rem;
|
|
530
|
+
font-size: 0.82rem;
|
|
531
|
+
line-height: 1.5;
|
|
532
|
+
color: var(--md-text-soft, var(--md-muted));
|
|
533
|
+
white-space: pre-wrap;
|
|
534
|
+
}
|
|
535
|
+
|
|
488
536
|
/* ── Context menu ─────────────────────────────────────────────────────── */
|
|
489
537
|
[data-scope='md-context'][data-part='backdrop'] {
|
|
490
538
|
position: fixed;
|