@meowdown/react 0.24.1 → 0.26.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/index.d.ts +18 -2
- package/dist/index.js +335 -28
- package/dist/style.css +89 -0
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { ReactElement, ReactNode, Ref } from "react";
|
|
2
|
-
import { ExitBoundaryHandler, ImageClickHandler, ImageOptions, LinkClickHandler, MarkMode, PlaceholderOptions, TagClickHandler, TypedEditor, WikilinkClickHandler } from "@meowdown/core";
|
|
2
|
+
import { ExitBoundaryHandler, ImageClickHandler, ImageOptions, LinkClickHandler, LinkCopyHandler, MarkMode, PlaceholderOptions, TagClickHandler, TypedEditor, WikilinkClickHandler } from "@meowdown/core";
|
|
3
3
|
import { SelectionJSON, SelectionJSON as SelectionJSON$1 } from "@prosekit/core";
|
|
4
4
|
import { useEditor, useExtension, useKeymap } from "@prosekit/react";
|
|
5
5
|
|
|
6
|
+
//#region src/utils/date-format.d.ts
|
|
7
|
+
type TimeFormat = '12' | '24';
|
|
8
|
+
//#endregion
|
|
6
9
|
//#region src/components/types.d.ts
|
|
7
10
|
/** A selection to restore: an exact JSON selection, or a document edge. */
|
|
8
11
|
type SelectionHint = SelectionJSON$1 | 'start' | 'end';
|
|
@@ -124,6 +127,12 @@ interface EditorProps {
|
|
|
124
127
|
* in source mode.
|
|
125
128
|
*/
|
|
126
129
|
onLinkClick?: LinkClickHandler;
|
|
130
|
+
/**
|
|
131
|
+
* Called after a link is copied from the link menu, with its `href`. Useful
|
|
132
|
+
* for a toast. Pass a stable function (e.g. from `useCallback`). Ignored in
|
|
133
|
+
* source mode.
|
|
134
|
+
*/
|
|
135
|
+
onLinkCopy?: LinkCopyHandler;
|
|
127
136
|
/**
|
|
128
137
|
* Called with the tag name (without the leading `#`) on click of a rendered
|
|
129
138
|
* `#tag`. Pass a stable function (e.g. from `useCallback`). Ignored in source
|
|
@@ -192,6 +201,11 @@ interface EditorProps {
|
|
|
192
201
|
* to the browser's behavior. Ignored in source mode.
|
|
193
202
|
*/
|
|
194
203
|
spellCheck?: boolean;
|
|
204
|
+
/**
|
|
205
|
+
* Clock format the `/now` slash command inserts: '12' for "3:45pm" or '24'
|
|
206
|
+
* for "15:45". Defaults to '12'. Ignored in source mode.
|
|
207
|
+
*/
|
|
208
|
+
timeFormat?: TimeFormat;
|
|
195
209
|
/** Class on the editable root (the contenteditable). Rich modes only. */
|
|
196
210
|
editorClassName?: string;
|
|
197
211
|
/** Class on the outer `.meowdown` wrapper div. */
|
|
@@ -209,6 +223,7 @@ declare function MeowdownEditor({
|
|
|
209
223
|
onWikilinkSearch,
|
|
210
224
|
onWikilinkClick,
|
|
211
225
|
onLinkClick,
|
|
226
|
+
onLinkCopy,
|
|
212
227
|
onTagClick,
|
|
213
228
|
onExitBoundary,
|
|
214
229
|
resolveImageUrl,
|
|
@@ -222,6 +237,7 @@ declare function MeowdownEditor({
|
|
|
222
237
|
placeholder,
|
|
223
238
|
readOnly,
|
|
224
239
|
spellCheck,
|
|
240
|
+
timeFormat,
|
|
225
241
|
editorClassName,
|
|
226
242
|
wrapperClassName,
|
|
227
243
|
handleRef,
|
|
@@ -269,4 +285,4 @@ declare function MarkdownView({
|
|
|
269
285
|
className
|
|
270
286
|
}: MarkdownViewProps): ReactElement;
|
|
271
287
|
//#endregion
|
|
272
|
-
export { type EditorHandle, type EditorMode, type EditorProps, type EditorStateSnapshot, MarkdownView, type MarkdownViewProps, MeowdownEditor, type SelectionHint, type SelectionJSON, type TagItem, type TagSearchHandler, type WikilinkItem, type WikilinkSearchHandler, useEditor, useExtension, useKeymap };
|
|
288
|
+
export { type EditorHandle, type EditorMode, type EditorProps, type EditorStateSnapshot, MarkdownView, type MarkdownViewProps, MeowdownEditor, type SelectionHint, type SelectionJSON, type TagItem, type TagSearchHandler, type TimeFormat, type WikilinkItem, type WikilinkSearchHandler, useEditor, useExtension, useKeymap };
|
package/dist/index.js
CHANGED
|
@@ -7,14 +7,15 @@ import { Compartment, EditorSelection, EditorState } from "@codemirror/state";
|
|
|
7
7
|
import { EditorView, keymap } from "@codemirror/view";
|
|
8
8
|
import { clamp } from "@ocavue/utils";
|
|
9
9
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
-
import { codeBlockLanguages, defaultResolveImageUrl, defineBulletAfterHeading, defineEditorExtension, defineEmbedPaste, defineExitBoundaryHandler, defineHTMLPaste, defineImage, defineImageClickHandler, defineLinkClickHandler, defineMarkMode, defineMarkdownCopy, definePlaceholder, defineReadonly, defineTagClickHandler, defineWikilinkClickHandler, defineWikilinkTrigger, docToMarkdown, getCodeTokens, getMarkBuilders, inlineTextToMarkChunks, listenForTweetHeight, markdownToDoc, matchEmbed } from "@meowdown/core";
|
|
10
|
+
import { codeBlockLanguages, defaultResolveImageUrl, defineBulletAfterHeading, defineEditorExtension, defineEmbedPaste, defineExitBoundaryHandler, defineHTMLPaste, defineImage, defineImageClickHandler, defineLinkClickHandler, defineLinkEditKeymap, defineLinkHoverHandler, defineMarkMode, defineMarkdownCopy, definePlaceholder, defineReadonly, defineTagClickHandler, defineWikilinkClickHandler, defineWikilinkTrigger, docToMarkdown, getCodeTokens, getMarkBuilders, getVirtualElementFromRange, inlineTextToMarkChunks, listenForTweetHeight, markdownToDoc, matchEmbed } from "@meowdown/core";
|
|
11
11
|
import { canUseRegexLookbehind, createEditor, defineDocChangeHandler, union } from "@prosekit/core";
|
|
12
12
|
import { Selection, TextSelection } from "@prosekit/pm/state";
|
|
13
13
|
import { ProseKit, defineReactNodeView, useEditor, useEditor as useEditor$1, useEditorDerivedValue, useExtension, useExtension as useExtension$1, useKeymap } from "@prosekit/react";
|
|
14
14
|
import { Combobox } from "@base-ui/react/combobox";
|
|
15
|
-
import { CheckIcon, ChevronsUpDownIcon, CopyIcon, GripHorizontalIcon, GripVerticalIcon } from "lucide-react";
|
|
15
|
+
import { CheckIcon, ChevronsUpDownIcon, CopyIcon, GripHorizontalIcon, GripVerticalIcon, PencilIcon, UnlinkIcon } from "lucide-react";
|
|
16
16
|
import { BlockHandleDraggable, BlockHandlePopup, BlockHandlePositioner, BlockHandleRoot } from "@prosekit/react/block-handle";
|
|
17
17
|
import { DropIndicator } from "@prosekit/react/drop-indicator";
|
|
18
|
+
import { Popover } from "@base-ui/react/popover";
|
|
18
19
|
import { AutocompleteEmpty, AutocompleteItem, AutocompletePopup, AutocompletePositioner, AutocompleteRoot } from "@prosekit/react/autocomplete";
|
|
19
20
|
import { MenuItem, MenuPopup, MenuPositioner } from "@prosekit/react/menu";
|
|
20
21
|
import { TableHandleColumnMenuRoot, TableHandleColumnMenuTrigger, TableHandleColumnPopup, TableHandleColumnPositioner, TableHandleDragPreview, TableHandleDropIndicator, TableHandleRoot, TableHandleRowMenuRoot, TableHandleRowMenuTrigger, TableHandleRowPopup, TableHandleRowPositioner } from "@prosekit/react/table-handle";
|
|
@@ -155,8 +156,41 @@ var code_block_view_module_default = {
|
|
|
155
156
|
};
|
|
156
157
|
|
|
157
158
|
//#endregion
|
|
158
|
-
//#region src/components/
|
|
159
|
+
//#region src/components/copy-button.tsx
|
|
159
160
|
const COPIED_RESET_MS = 1500;
|
|
161
|
+
/**
|
|
162
|
+
* A copy-to-clipboard button with "copied" feedback. Shared by the code block
|
|
163
|
+
* toolbar and the link popover.
|
|
164
|
+
*/
|
|
165
|
+
function CopyButton({ getText, label, onCopy, className, ...rest }) {
|
|
166
|
+
const [copied, setCopied] = useState(false);
|
|
167
|
+
const resetTimerRef = useRef(void 0);
|
|
168
|
+
const copy = async () => {
|
|
169
|
+
try {
|
|
170
|
+
await navigator.clipboard.writeText(getText());
|
|
171
|
+
setCopied(true);
|
|
172
|
+
clearTimeout(resetTimerRef.current);
|
|
173
|
+
resetTimerRef.current = setTimeout(() => setCopied(false), COPIED_RESET_MS);
|
|
174
|
+
onCopy?.();
|
|
175
|
+
} catch (error) {
|
|
176
|
+
console.warn("[meowdown] Failed to copy:", error);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
return /* @__PURE__ */ jsx("button", {
|
|
180
|
+
type: "button",
|
|
181
|
+
className,
|
|
182
|
+
"data-copied": copied ? "" : void 0,
|
|
183
|
+
"aria-label": copied ? "Copied" : label,
|
|
184
|
+
title: copied ? "Copied" : label,
|
|
185
|
+
onMouseDown: (event) => event.preventDefault(),
|
|
186
|
+
onClick: copy,
|
|
187
|
+
...rest,
|
|
188
|
+
children: copied ? /* @__PURE__ */ jsx(CheckIcon, {}) : /* @__PURE__ */ jsx(CopyIcon, {})
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
//#endregion
|
|
193
|
+
//#region src/components/code-block-view.tsx
|
|
160
194
|
function CodeBlockView(props) {
|
|
161
195
|
const language = props.node.attrs.language || "";
|
|
162
196
|
const selected = useMemo(() => {
|
|
@@ -179,18 +213,6 @@ function CodeBlockView(props) {
|
|
|
179
213
|
const setLanguage = (item) => {
|
|
180
214
|
props.setAttrs({ language: item?.value ?? "" });
|
|
181
215
|
};
|
|
182
|
-
const [copied, setCopied] = useState(false);
|
|
183
|
-
const resetTimerRef = useRef(void 0);
|
|
184
|
-
const copy = async () => {
|
|
185
|
-
try {
|
|
186
|
-
await navigator.clipboard.writeText(props.node.textContent);
|
|
187
|
-
setCopied(true);
|
|
188
|
-
clearTimeout(resetTimerRef.current);
|
|
189
|
-
resetTimerRef.current = setTimeout(() => setCopied(false), COPIED_RESET_MS);
|
|
190
|
-
} catch (error) {
|
|
191
|
-
console.warn("[meowdown] Failed to copy code block:", error);
|
|
192
|
-
}
|
|
193
|
-
};
|
|
194
216
|
return /* @__PURE__ */ jsxs("div", {
|
|
195
217
|
className: code_block_view_module_default.Root,
|
|
196
218
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
@@ -252,16 +274,11 @@ function CodeBlockView(props) {
|
|
|
252
274
|
]
|
|
253
275
|
})
|
|
254
276
|
}) })]
|
|
255
|
-
}), /* @__PURE__ */ jsx(
|
|
256
|
-
|
|
277
|
+
}), /* @__PURE__ */ jsx(CopyButton, {
|
|
278
|
+
getText: () => props.node.textContent,
|
|
279
|
+
label: "Copy code",
|
|
257
280
|
className: code_block_view_module_default.CopyButton,
|
|
258
|
-
"data-testid": "code-block-copy"
|
|
259
|
-
"data-copied": copied ? "" : void 0,
|
|
260
|
-
"aria-label": copied ? "Copied" : "Copy code",
|
|
261
|
-
title: copied ? "Copied" : "Copy code",
|
|
262
|
-
onMouseDown: (event) => event.preventDefault(),
|
|
263
|
-
onClick: copy,
|
|
264
|
-
children: copied ? /* @__PURE__ */ jsx(CheckIcon, {}) : /* @__PURE__ */ jsx(CopyIcon, {})
|
|
281
|
+
"data-testid": "code-block-copy"
|
|
265
282
|
})]
|
|
266
283
|
}), /* @__PURE__ */ jsx("pre", {
|
|
267
284
|
ref: props.contentRef,
|
|
@@ -376,6 +393,286 @@ function EditorExtensions({ markMode, onDocChange, onWikilinkClick, onLinkClick,
|
|
|
376
393
|
return null;
|
|
377
394
|
}
|
|
378
395
|
|
|
396
|
+
//#endregion
|
|
397
|
+
//#region src/hooks/use-delayed-flag.ts
|
|
398
|
+
/** Delay before the flag opens, in ms. */
|
|
399
|
+
const OPEN_DELAY = 400;
|
|
400
|
+
/** Grace before the flag closes, in ms. The window lets a pointer travel from
|
|
401
|
+
* the hovered link onto the popover it anchors. */
|
|
402
|
+
const CLOSE_DELAY = 300;
|
|
403
|
+
/**
|
|
404
|
+
* Mirrors `value` into a boolean that flips true `openDelay`ms after `value`
|
|
405
|
+
* becomes true and false `closeDelay`ms after it becomes false, cancelling any
|
|
406
|
+
* pending flip on each change.
|
|
407
|
+
*/
|
|
408
|
+
function useDelayedFlag(value, openDelay = OPEN_DELAY, closeDelay = CLOSE_DELAY) {
|
|
409
|
+
const [flag, setFlag] = useState(false);
|
|
410
|
+
const timerRef = useRef(void 0);
|
|
411
|
+
useEffect(() => {
|
|
412
|
+
clearTimeout(timerRef.current);
|
|
413
|
+
timerRef.current = setTimeout(() => setFlag(value), value ? openDelay : closeDelay);
|
|
414
|
+
return () => clearTimeout(timerRef.current);
|
|
415
|
+
}, [
|
|
416
|
+
value,
|
|
417
|
+
openDelay,
|
|
418
|
+
closeDelay
|
|
419
|
+
]);
|
|
420
|
+
return flag;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
//#endregion
|
|
424
|
+
//#region src/components/link-menu.module.css
|
|
425
|
+
var link_menu_module_default = {
|
|
426
|
+
"Button": "meow_Button_IjcqKG",
|
|
427
|
+
"Form": "meow_Form_IjcqKG",
|
|
428
|
+
"Input": "meow_Input_IjcqKG",
|
|
429
|
+
"Popup": "meow_Popup_IjcqKG",
|
|
430
|
+
"Positioner": "meow_Positioner_IjcqKG",
|
|
431
|
+
"Row": "meow_Row_IjcqKG",
|
|
432
|
+
"SrOnly": "meow_SrOnly_IjcqKG",
|
|
433
|
+
"Url": "meow_Url_IjcqKG"
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
//#endregion
|
|
437
|
+
//#region src/components/link-menu.tsx
|
|
438
|
+
/** Select the link unit so the text-backed commands target it, and keep the
|
|
439
|
+
* editor focused so its virtual selection stays visible behind the popover. */
|
|
440
|
+
function selectLinkUnit(editor, link) {
|
|
441
|
+
editor.commands.selectText(link.unit.from, link.unit.to);
|
|
442
|
+
editor.focus();
|
|
443
|
+
}
|
|
444
|
+
/** A Base UI popover anchored at `anchor`. Base UI dismisses it on an outside
|
|
445
|
+
* press or Escape, both routed through `onClose`. */
|
|
446
|
+
function LinkPopover({ anchor, onClose, onPopupHover, children }) {
|
|
447
|
+
return /* @__PURE__ */ jsx(Popover.Root, {
|
|
448
|
+
open: true,
|
|
449
|
+
onOpenChange: (open) => {
|
|
450
|
+
if (!open) onClose();
|
|
451
|
+
},
|
|
452
|
+
children: /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsx(Popover.Positioner, {
|
|
453
|
+
anchor,
|
|
454
|
+
side: "bottom",
|
|
455
|
+
sideOffset: 8,
|
|
456
|
+
className: link_menu_module_default.Positioner,
|
|
457
|
+
children: /* @__PURE__ */ jsx(Popover.Popup, {
|
|
458
|
+
className: link_menu_module_default.Popup,
|
|
459
|
+
"data-testid": "link-popover",
|
|
460
|
+
initialFocus: false,
|
|
461
|
+
finalFocus: false,
|
|
462
|
+
onMouseEnter: () => onPopupHover?.(true),
|
|
463
|
+
onMouseLeave: () => onPopupHover?.(false),
|
|
464
|
+
children
|
|
465
|
+
})
|
|
466
|
+
}) })
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
/** The hover preview: the url plus copy, edit, and remove actions. */
|
|
470
|
+
function LinkInfoContent({ href, onLinkClick, onLinkCopy, onEdit, onRemove }) {
|
|
471
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
472
|
+
className: link_menu_module_default.Row,
|
|
473
|
+
"data-testid": "link-popover-read",
|
|
474
|
+
children: [
|
|
475
|
+
/* @__PURE__ */ jsx("a", {
|
|
476
|
+
className: link_menu_module_default.Url,
|
|
477
|
+
href,
|
|
478
|
+
title: href,
|
|
479
|
+
target: "_blank",
|
|
480
|
+
rel: "noopener noreferrer",
|
|
481
|
+
onClick: (event) => {
|
|
482
|
+
if (!onLinkClick) return;
|
|
483
|
+
event.preventDefault();
|
|
484
|
+
onLinkClick({
|
|
485
|
+
href,
|
|
486
|
+
event: event.nativeEvent
|
|
487
|
+
});
|
|
488
|
+
},
|
|
489
|
+
children: href
|
|
490
|
+
}),
|
|
491
|
+
/* @__PURE__ */ jsx(CopyButton, {
|
|
492
|
+
getText: () => href,
|
|
493
|
+
label: "Copy link",
|
|
494
|
+
className: link_menu_module_default.Button,
|
|
495
|
+
onCopy: () => onLinkCopy?.({ href })
|
|
496
|
+
}),
|
|
497
|
+
/* @__PURE__ */ jsx("button", {
|
|
498
|
+
type: "button",
|
|
499
|
+
className: link_menu_module_default.Button,
|
|
500
|
+
title: "Edit link",
|
|
501
|
+
"aria-label": "Edit link",
|
|
502
|
+
onClick: onEdit,
|
|
503
|
+
children: /* @__PURE__ */ jsx(PencilIcon, {})
|
|
504
|
+
}),
|
|
505
|
+
/* @__PURE__ */ jsx("button", {
|
|
506
|
+
type: "button",
|
|
507
|
+
className: link_menu_module_default.Button,
|
|
508
|
+
title: "Remove link",
|
|
509
|
+
"aria-label": "Remove link",
|
|
510
|
+
onClick: onRemove,
|
|
511
|
+
children: /* @__PURE__ */ jsx(UnlinkIcon, {})
|
|
512
|
+
})
|
|
513
|
+
]
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
/** The url and title form, opened by `Mod-k` or the preview's edit button. */
|
|
517
|
+
function LinkEditContent({ link, onSubmit }) {
|
|
518
|
+
const hrefInputRef = useRef(null);
|
|
519
|
+
const titleInputRef = useRef(null);
|
|
520
|
+
const href = link ? link.href : "";
|
|
521
|
+
const title = link ? link.title : "";
|
|
522
|
+
useEffect(() => {
|
|
523
|
+
hrefInputRef.current?.focus();
|
|
524
|
+
}, []);
|
|
525
|
+
return /* @__PURE__ */ jsxs("form", {
|
|
526
|
+
className: link_menu_module_default.Form,
|
|
527
|
+
"data-testid": "link-popover-edit",
|
|
528
|
+
onSubmit: (event) => {
|
|
529
|
+
event.preventDefault();
|
|
530
|
+
onSubmit(hrefInputRef.current?.value || "", titleInputRef.current?.value || "");
|
|
531
|
+
},
|
|
532
|
+
children: [
|
|
533
|
+
/* @__PURE__ */ jsx("input", {
|
|
534
|
+
ref: hrefInputRef,
|
|
535
|
+
className: link_menu_module_default.Input,
|
|
536
|
+
defaultValue: href,
|
|
537
|
+
placeholder: "Paste link...",
|
|
538
|
+
"data-testid": "link-popover-input"
|
|
539
|
+
}),
|
|
540
|
+
/* @__PURE__ */ jsx("input", {
|
|
541
|
+
ref: titleInputRef,
|
|
542
|
+
className: link_menu_module_default.Input,
|
|
543
|
+
defaultValue: title,
|
|
544
|
+
placeholder: "Title (optional)"
|
|
545
|
+
}),
|
|
546
|
+
/* @__PURE__ */ jsx("button", {
|
|
547
|
+
type: "submit",
|
|
548
|
+
className: link_menu_module_default.SrOnly,
|
|
549
|
+
"data-testid": "link-popover-submit",
|
|
550
|
+
children: "Save"
|
|
551
|
+
})
|
|
552
|
+
]
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Owns both link triggers and shows one popover at a time:
|
|
557
|
+
*
|
|
558
|
+
* - hovering a link opens a read-only preview that follows the pointer;
|
|
559
|
+
* - `Mod-k` (or the preview's edit button) opens an edit form that stays until
|
|
560
|
+
* it is submitted, dismissed with Escape, or pressed outside.
|
|
561
|
+
*/
|
|
562
|
+
function LinkMenu({ onLinkClick, onLinkCopy }) {
|
|
563
|
+
const editor = useEditor$1();
|
|
564
|
+
const [hover, setHover] = useState();
|
|
565
|
+
const [onLink, setOnLink] = useState(false);
|
|
566
|
+
const [overPopup, setOverPopup] = useState(false);
|
|
567
|
+
const [edit, setEdit] = useState();
|
|
568
|
+
const hoverOpen = useDelayedFlag(onLink || overPopup);
|
|
569
|
+
useExtension$1(useMemo(() => {
|
|
570
|
+
return defineLinkHoverHandler((hit) => {
|
|
571
|
+
setOnLink(!!hit);
|
|
572
|
+
if (hit) setHover(hit.payload);
|
|
573
|
+
});
|
|
574
|
+
}, []));
|
|
575
|
+
useExtension$1(useMemo(() => {
|
|
576
|
+
return defineLinkEditKeymap((options) => {
|
|
577
|
+
setEdit(options);
|
|
578
|
+
});
|
|
579
|
+
}, []));
|
|
580
|
+
const closeHover = useCallback(() => {
|
|
581
|
+
setOnLink(false);
|
|
582
|
+
setOverPopup(false);
|
|
583
|
+
setHover(void 0);
|
|
584
|
+
}, []);
|
|
585
|
+
const closeEdit = useCallback(() => {
|
|
586
|
+
setEdit(void 0);
|
|
587
|
+
closeHover();
|
|
588
|
+
editor.focus();
|
|
589
|
+
}, [editor, closeHover]);
|
|
590
|
+
let rangeFrom;
|
|
591
|
+
let rangeTo;
|
|
592
|
+
if (edit) {
|
|
593
|
+
rangeFrom = edit.from;
|
|
594
|
+
rangeTo = edit.to;
|
|
595
|
+
} else if (hover) {
|
|
596
|
+
rangeFrom = hover.unit.from;
|
|
597
|
+
rangeTo = hover.unit.to;
|
|
598
|
+
}
|
|
599
|
+
const anchor = useMemo(() => {
|
|
600
|
+
if (rangeFrom == null || rangeTo == null) return;
|
|
601
|
+
return getVirtualElementFromRange(editor.view, {
|
|
602
|
+
from: rangeFrom,
|
|
603
|
+
to: rangeTo
|
|
604
|
+
});
|
|
605
|
+
}, [
|
|
606
|
+
rangeFrom,
|
|
607
|
+
rangeTo,
|
|
608
|
+
editor
|
|
609
|
+
]);
|
|
610
|
+
if (edit) return /* @__PURE__ */ jsx(LinkPopover, {
|
|
611
|
+
anchor,
|
|
612
|
+
onClose: closeEdit,
|
|
613
|
+
children: /* @__PURE__ */ jsx(LinkEditContent, {
|
|
614
|
+
link: edit.link,
|
|
615
|
+
onSubmit: (href, title) => {
|
|
616
|
+
if (edit.link) if (href.trim()) editor.commands.updateLink({
|
|
617
|
+
href,
|
|
618
|
+
title
|
|
619
|
+
});
|
|
620
|
+
else editor.commands.removeLink();
|
|
621
|
+
else if (href.trim()) editor.commands.insertLink({
|
|
622
|
+
href,
|
|
623
|
+
title
|
|
624
|
+
});
|
|
625
|
+
closeEdit();
|
|
626
|
+
}
|
|
627
|
+
})
|
|
628
|
+
});
|
|
629
|
+
if (hoverOpen && hover) {
|
|
630
|
+
const link = hover;
|
|
631
|
+
return /* @__PURE__ */ jsx(LinkPopover, {
|
|
632
|
+
anchor,
|
|
633
|
+
onClose: closeHover,
|
|
634
|
+
onPopupHover: setOverPopup,
|
|
635
|
+
children: /* @__PURE__ */ jsx(LinkInfoContent, {
|
|
636
|
+
href: link.href,
|
|
637
|
+
onLinkClick,
|
|
638
|
+
onLinkCopy,
|
|
639
|
+
onEdit: () => {
|
|
640
|
+
selectLinkUnit(editor, link);
|
|
641
|
+
setEdit({
|
|
642
|
+
from: link.unit.from,
|
|
643
|
+
to: link.unit.to,
|
|
644
|
+
link
|
|
645
|
+
});
|
|
646
|
+
closeHover();
|
|
647
|
+
},
|
|
648
|
+
onRemove: () => {
|
|
649
|
+
selectLinkUnit(editor, link);
|
|
650
|
+
editor.commands.removeLink();
|
|
651
|
+
closeHover();
|
|
652
|
+
}
|
|
653
|
+
})
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
return null;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
//#endregion
|
|
660
|
+
//#region src/utils/date-format.ts
|
|
661
|
+
/** Formats the current wall-clock time for the `/now` slash command. */
|
|
662
|
+
function formatNowTime(timeFormat) {
|
|
663
|
+
return formatTime(/* @__PURE__ */ new Date(), timeFormat);
|
|
664
|
+
}
|
|
665
|
+
/** Formats a given time as `3:45pm` ('12') or `15:45` ('24'). */
|
|
666
|
+
function formatTime(date, timeFormat) {
|
|
667
|
+
return timeFormat === "12" ? formatTime12(date) : formatTime24(date);
|
|
668
|
+
}
|
|
669
|
+
function formatTime12(date) {
|
|
670
|
+
return `${date.getHours() % 12 || 12}:${date.getMinutes().toString().padStart(2, "0")}${date.getHours() >= 12 ? "pm" : "am"}`;
|
|
671
|
+
}
|
|
672
|
+
function formatTime24(date) {
|
|
673
|
+
return `${date.getHours().toString().padStart(2, "0")}:${date.getMinutes().toString().padStart(2, "0")}`;
|
|
674
|
+
}
|
|
675
|
+
|
|
379
676
|
//#endregion
|
|
380
677
|
//#region src/components/autocomplete-menu.module.css
|
|
381
678
|
var autocomplete_menu_module_default = {
|
|
@@ -396,7 +693,7 @@ function SlashMenuItem({ label, kbd, onSelect }) {
|
|
|
396
693
|
children: [/* @__PURE__ */ jsx("span", { children: label }), kbd && /* @__PURE__ */ jsx("kbd", { children: kbd })]
|
|
397
694
|
});
|
|
398
695
|
}
|
|
399
|
-
function SlashMenu() {
|
|
696
|
+
function SlashMenu({ timeFormat = "12" }) {
|
|
400
697
|
const editor = useEditor$1();
|
|
401
698
|
return /* @__PURE__ */ jsx(AutocompleteRoot, {
|
|
402
699
|
regex: regex$2,
|
|
@@ -464,6 +761,10 @@ function SlashMenu() {
|
|
|
464
761
|
header: true
|
|
465
762
|
})
|
|
466
763
|
}),
|
|
764
|
+
/* @__PURE__ */ jsx(SlashMenuItem, {
|
|
765
|
+
label: "Now",
|
|
766
|
+
onSelect: () => editor.commands.insertText({ text: formatNowTime(timeFormat) })
|
|
767
|
+
}),
|
|
467
768
|
/* @__PURE__ */ jsx(AutocompleteEmpty, {
|
|
468
769
|
className: autocomplete_menu_module_default.Item,
|
|
469
770
|
children: "No results"
|
|
@@ -780,7 +1081,7 @@ function resolveSelection(doc, selection) {
|
|
|
780
1081
|
return TextSelection.between(doc.resolve(anchor), doc.resolve(head));
|
|
781
1082
|
}
|
|
782
1083
|
}
|
|
783
|
-
function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onTagSearch, onWikilinkSearch, onWikilinkClick, onLinkClick, onTagClick, onExitBoundary, resolveImageUrl, onImagePaste, onImageSaveError, onImageClick, embedPaste, bulletAfterHeading, frontmatter = false, blockHandle = true, placeholder, readOnly, spellCheck, editorClassName, ref, children }) {
|
|
1084
|
+
function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onTagSearch, onWikilinkSearch, onWikilinkClick, onLinkClick, onLinkCopy, onTagClick, onExitBoundary, resolveImageUrl, onImagePaste, onImageSaveError, onImageClick, embedPaste, bulletAfterHeading, frontmatter = false, blockHandle = true, placeholder, readOnly, spellCheck, timeFormat, editorClassName, ref, children }) {
|
|
784
1085
|
const [editor] = useState(() => {
|
|
785
1086
|
const editor = createEditor({ extension: union(defineEditorExtension(), defineCodeBlockView()) });
|
|
786
1087
|
if (initialMarkdown) editor.setContent(markdownToDoc(initialMarkdown, {
|
|
@@ -877,7 +1178,11 @@ function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onTa
|
|
|
877
1178
|
blockHandle && !readOnly && /* @__PURE__ */ jsx(BlockHandle, {}),
|
|
878
1179
|
!readOnly && /* @__PURE__ */ jsx(TableHandle, {}),
|
|
879
1180
|
blockHandle && !readOnly && /* @__PURE__ */ jsx(DropIndicator$1, {}),
|
|
880
|
-
/* @__PURE__ */ jsx(SlashMenu, {}),
|
|
1181
|
+
/* @__PURE__ */ jsx(SlashMenu, { timeFormat }),
|
|
1182
|
+
!readOnly && /* @__PURE__ */ jsx(LinkMenu, {
|
|
1183
|
+
onLinkClick,
|
|
1184
|
+
onLinkCopy
|
|
1185
|
+
}),
|
|
881
1186
|
onTagSearch && /* @__PURE__ */ jsx(TagMenu, { onTagSearch }),
|
|
882
1187
|
onWikilinkSearch && /* @__PURE__ */ jsx(WikilinkMenu, { onWikilinkSearch }),
|
|
883
1188
|
children
|
|
@@ -887,7 +1192,7 @@ function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onTa
|
|
|
887
1192
|
|
|
888
1193
|
//#endregion
|
|
889
1194
|
//#region src/components/editor.tsx
|
|
890
|
-
function MeowdownEditor({ mode = "focus", initialMarkdown, onDocChange, onTagSearch, onWikilinkSearch, onWikilinkClick, onLinkClick, onTagClick, onExitBoundary, resolveImageUrl, onImagePaste, onImageSaveError, onImageClick, embedPaste = true, bulletAfterHeading = false, frontmatter = false, blockHandle = true, placeholder, readOnly, spellCheck, editorClassName, wrapperClassName, handleRef, children }) {
|
|
1195
|
+
function MeowdownEditor({ mode = "focus", initialMarkdown, onDocChange, onTagSearch, onWikilinkSearch, onWikilinkClick, onLinkClick, onLinkCopy, onTagClick, onExitBoundary, resolveImageUrl, onImagePaste, onImageSaveError, onImageClick, embedPaste = true, bulletAfterHeading = false, frontmatter = false, blockHandle = true, placeholder, readOnly, spellCheck, timeFormat, editorClassName, wrapperClassName, handleRef, children }) {
|
|
891
1196
|
const childRef = useRef(null);
|
|
892
1197
|
useImperativeHandle(handleRef, () => {
|
|
893
1198
|
function getMarkdown() {
|
|
@@ -953,6 +1258,7 @@ function MeowdownEditor({ mode = "focus", initialMarkdown, onDocChange, onTagSea
|
|
|
953
1258
|
onWikilinkSearch,
|
|
954
1259
|
onWikilinkClick,
|
|
955
1260
|
onLinkClick,
|
|
1261
|
+
onLinkCopy,
|
|
956
1262
|
onTagClick,
|
|
957
1263
|
onExitBoundary,
|
|
958
1264
|
resolveImageUrl,
|
|
@@ -966,6 +1272,7 @@ function MeowdownEditor({ mode = "focus", initialMarkdown, onDocChange, onTagSea
|
|
|
966
1272
|
placeholder,
|
|
967
1273
|
readOnly,
|
|
968
1274
|
spellCheck,
|
|
1275
|
+
timeFormat,
|
|
969
1276
|
editorClassName,
|
|
970
1277
|
children
|
|
971
1278
|
})
|
package/dist/style.css
CHANGED
|
@@ -255,6 +255,95 @@
|
|
|
255
255
|
background: var(--meowdown-accent);
|
|
256
256
|
transition: all .15s;
|
|
257
257
|
}
|
|
258
|
+
.meow_Positioner_IjcqKG {
|
|
259
|
+
z-index: 50;
|
|
260
|
+
width: min(20rem, 100vw - 1rem);
|
|
261
|
+
display: block;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.meow_Popup_IjcqKG {
|
|
265
|
+
box-sizing: border-box;
|
|
266
|
+
border: 1px solid var(--meowdown-border);
|
|
267
|
+
background: var(--meowdown-link-popover-bg, light-dark(#fff, #18181b));
|
|
268
|
+
border-radius: .75rem;
|
|
269
|
+
flex-direction: column;
|
|
270
|
+
width: 100%;
|
|
271
|
+
padding: .25rem;
|
|
272
|
+
font-size: .875rem;
|
|
273
|
+
display: flex;
|
|
274
|
+
box-shadow: 0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.meow_Row_IjcqKG {
|
|
278
|
+
align-items: center;
|
|
279
|
+
gap: .125rem;
|
|
280
|
+
display: flex;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.meow_Url_IjcqKG {
|
|
284
|
+
white-space: nowrap;
|
|
285
|
+
text-overflow: ellipsis;
|
|
286
|
+
min-width: 0;
|
|
287
|
+
color: var(--meowdown-accent);
|
|
288
|
+
cursor: pointer;
|
|
289
|
+
flex: 1;
|
|
290
|
+
padding: .25rem .5rem;
|
|
291
|
+
overflow: hidden;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.meow_Button_IjcqKG {
|
|
295
|
+
color: var(--meowdown-text);
|
|
296
|
+
cursor: pointer;
|
|
297
|
+
border-radius: .5rem;
|
|
298
|
+
flex: none;
|
|
299
|
+
justify-content: center;
|
|
300
|
+
align-items: center;
|
|
301
|
+
padding: .375rem;
|
|
302
|
+
display: inline-flex;
|
|
303
|
+
|
|
304
|
+
&:hover {
|
|
305
|
+
background: var(--meowdown-link-popover-hover-bg, light-dark(#f4f4f5, #27272a));
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
& svg {
|
|
309
|
+
width: 1rem;
|
|
310
|
+
height: 1rem;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.meow_Form_IjcqKG {
|
|
315
|
+
flex-direction: column;
|
|
316
|
+
gap: .25rem;
|
|
317
|
+
padding: .25rem;
|
|
318
|
+
display: flex;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.meow_SrOnly_IjcqKG {
|
|
322
|
+
clip: rect(0, 0, 0, 0);
|
|
323
|
+
border: 0;
|
|
324
|
+
width: 1px;
|
|
325
|
+
height: 1px;
|
|
326
|
+
margin: -1px;
|
|
327
|
+
padding: 0;
|
|
328
|
+
position: absolute;
|
|
329
|
+
overflow: hidden;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.meow_Input_IjcqKG {
|
|
333
|
+
box-sizing: border-box;
|
|
334
|
+
width: 100%;
|
|
335
|
+
font: inherit;
|
|
336
|
+
color: var(--meowdown-text);
|
|
337
|
+
border: 1px solid var(--meowdown-border);
|
|
338
|
+
background: none;
|
|
339
|
+
border-radius: .5rem;
|
|
340
|
+
padding: .375rem .5rem;
|
|
341
|
+
|
|
342
|
+
&:focus {
|
|
343
|
+
outline: 2px solid var(--meowdown-accent);
|
|
344
|
+
outline-offset: -1px;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
258
347
|
.meow_Positioner_Dqll0G {
|
|
259
348
|
z-index: 50;
|
|
260
349
|
width: min(24rem, 100vw - 1rem);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meowdown/react",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.26.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"@prosekit/react": "^0.8.0-beta.11",
|
|
31
31
|
"clsx": "^2.1.1",
|
|
32
32
|
"lucide-react": "^1.21.0",
|
|
33
|
-
"@meowdown/core": "0.
|
|
33
|
+
"@meowdown/core": "0.26.0"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"react": "^19.0.0",
|