leepi 0.0.3 → 0.0.4

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/CHANGELOG.md ADDED
@@ -0,0 +1,20 @@
1
+ # leepi
2
+
3
+ ## 0.0.4
4
+
5
+ ### Patch Changes
6
+
7
+ - e25ae8e: Fix bug with ul/ol toggles
8
+ - e25ae8e: Use link paste primitive from library instead of custom
9
+
10
+ ## 0.0.3
11
+
12
+ ### Patch Changes
13
+
14
+ - 2aa717f: Build package first before publishing
15
+
16
+ ## 0.0.2
17
+
18
+ ### Patch Changes
19
+
20
+ - 5778861: A headless markdown editor using CodeMirror with keyboard shortcuts and built-in React primitives
@@ -209,9 +209,15 @@ function toggleHeading(level) {
209
209
  };
210
210
  }
211
211
  const TASK_RE = /^(\s*)[-*+]\s\[([ xX])\]\s/;
212
+ const UL_RE = /^(\s*)[-*+]\s/;
213
+ const OL_RE = /^(\s*)\d+\.\s/;
212
214
  function detectListKind(state, pos, contentText) {
213
- let node = syntaxTree(state).resolveInner(pos, 1);
214
215
  const indent = contentText.match(/^(\s*)/)[1];
216
+ if (!UL_RE.test(contentText) && !OL_RE.test(contentText) && !TASK_RE.test(contentText)) return {
217
+ kind: "none",
218
+ indent: ""
219
+ };
220
+ let node = syntaxTree(state).resolveInner(pos, 1);
215
221
  while (node) {
216
222
  if (node.name === "ListItem" && node.getChild("Task")) return {
217
223
  kind: "task",
@@ -46,6 +46,7 @@ const codeBlockTheme = EditorView.theme({
46
46
  },
47
47
  ".cm-has-deco::before": {
48
48
  content: "attr(data-line-num)",
49
+ boxSizing: "content-box",
49
50
  position: "absolute",
50
51
  right: "calc(100% + 0.5ch)",
51
52
  width: "calc(var(--line-digits, 3) * 1ch)",
@@ -18,7 +18,6 @@ interface LinkPluginOptions {
18
18
  insertLink?: string;
19
19
  removeLink?: string;
20
20
  };
21
- urlRegex?: RegExp;
22
21
  }
23
22
  declare function linkPlugin(options?: LinkPluginOptions): Extension;
24
23
  //#endregion
@@ -1,17 +1,17 @@
1
1
  import { markRegistry, shortcutRegistry } from "../registry.js";
2
2
  import { isInsideCodeBlock } from "../utils.js";
3
- import { findLinkAtCursor, insertLink } from "../commands.js";
3
+ import { findLinkAtCursor } from "../commands.js";
4
4
  import { openPopover } from "../popover.js";
5
- import { EditorView, keymap } from "@codemirror/view";
5
+ import { keymap } from "@codemirror/view";
6
6
  import { HighlightStyle, syntaxHighlighting } from "@codemirror/language";
7
7
  import { EditorSelection } from "@codemirror/state";
8
+ import { pasteURLAsLink } from "@codemirror/lang-markdown";
8
9
  import { tags } from "@lezer/highlight";
9
10
  //#region src/core/plugins/link.ts
10
11
  const defaultShortcuts = {
11
12
  insertLink: "Mod-k",
12
13
  removeLink: "Mod-Shift-k"
13
14
  };
14
- const URL_RE = /^(?:https?:\/\/|mailto:|tel:)\S+$/;
15
15
  const linkHighlight = HighlightStyle.define([{
16
16
  tag: tags.link,
17
17
  color: "var(--lp-color-accent)"
@@ -24,20 +24,9 @@ function linkPlugin(options) {
24
24
  ...defaultShortcuts,
25
25
  ...options?.shortcuts
26
26
  };
27
- const urlRegex = options?.urlRegex ?? URL_RE;
28
27
  return [
29
28
  syntaxHighlighting(linkHighlight),
30
- EditorView.domEventHandlers({ paste(event, view) {
31
- const range = view.state.selection.main;
32
- if (range.empty) return false;
33
- if (isInsideCodeBlock(view)) return false;
34
- const text = event.clipboardData?.getData("text/plain")?.trim();
35
- if (!text || !urlRegex.test(text)) return false;
36
- event.preventDefault();
37
- const label = view.state.sliceDoc(range.from, range.to);
38
- insertLink(view, range.from, range.to, label, text);
39
- return true;
40
- } }),
29
+ pasteURLAsLink,
41
30
  shortcutRegistry.of([{
42
31
  key: keys.insertLink,
43
32
  action: "insertLink",
@@ -15,8 +15,8 @@ const blockCommands = {
15
15
  heading1: toggleHeading(1),
16
16
  heading2: toggleHeading(2),
17
17
  heading3: toggleHeading(3),
18
- orderedList: toggleListKind("ul"),
19
- unorderedList: toggleListKind("ol"),
18
+ orderedList: toggleListKind("ol"),
19
+ unorderedList: toggleListKind("ul"),
20
20
  taskList: toggleListKind("task"),
21
21
  blockquote: toggleBlockquote
22
22
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leepi",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "A composable React markdown editor with inline styling",
5
5
  "repository": {
6
6
  "type": "git",
@@ -11,7 +11,8 @@
11
11
  "dist",
12
12
  "src",
13
13
  "!src/**/*.test.ts",
14
- "!src/**/test-helpers.ts"
14
+ "!src/**/test-helpers.ts",
15
+ "CHANGELOG.md"
15
16
  ],
16
17
  "type": "module",
17
18
  "exports": {
@@ -236,14 +236,25 @@ const TASK_RE = /^(\s*)[-*+]\s\[([ xX])\]\s/;
236
236
 
237
237
  type ListKind = "ul" | "ol" | "task" | "none";
238
238
 
239
+ const UL_RE = /^(\s*)[-*+]\s/;
240
+ const OL_RE = /^(\s*)\d+\.\s/;
241
+
239
242
  function detectListKind(
240
243
  state: EditorState,
241
244
  pos: number,
242
245
  contentText: string,
243
246
  ): { kind: ListKind; indent: string } {
247
+ const indent = contentText.match(/^(\s*)/)![1];
248
+
249
+ // Check the line text first — if it doesn't have a list prefix,
250
+ // it's not a list item even if the syntax tree thinks it's inside a list
251
+ // (e.g. continuation paragraph after a list item).
252
+ if (!UL_RE.test(contentText) && !OL_RE.test(contentText) && !TASK_RE.test(contentText)) {
253
+ return { kind: "none", indent: "" };
254
+ }
255
+
244
256
  const tree = syntaxTree(state);
245
257
  let node = tree.resolveInner(pos, 1);
246
- const indent = contentText.match(/^(\s*)/)![1];
247
258
 
248
259
  while (node) {
249
260
  if (node.name === "ListItem" && node.getChild("Task")) {
@@ -89,6 +89,7 @@ const codeBlockTheme = EditorView.theme({
89
89
  },
90
90
  ".cm-has-deco::before": {
91
91
  content: "attr(data-line-num)",
92
+ boxSizing: "content-box",
92
93
  position: "absolute",
93
94
  right: "calc(100% + 0.5ch)",
94
95
  width: "calc(var(--line-digits, 3) * 1ch)",
@@ -1,12 +1,13 @@
1
- import { type Extension, EditorSelection } from "@codemirror/state";
2
- import { keymap, EditorView } from "@codemirror/view";
1
+ import { pasteURLAsLink } from "@codemirror/lang-markdown";
3
2
  import { HighlightStyle, syntaxHighlighting } from "@codemirror/language";
3
+ import { type Extension, EditorSelection } from "@codemirror/state";
4
+ import { keymap } from "@codemirror/view";
4
5
  import { tags } from "@lezer/highlight";
5
- import { shortcutRegistry, markRegistry } from "../registry";
6
6
  import { findLinkAtCursor, insertLink } from "../commands";
7
- import { isInsideCodeBlock } from "../utils";
8
7
  import { openPopover } from "../popover";
8
+ import { markRegistry, shortcutRegistry } from "../registry";
9
9
  import type { PopoverRequest } from "../types";
10
+ import { isInsideCodeBlock } from "../utils";
10
11
 
11
12
  export interface LinkData {
12
13
  /** Selected text or existing link label */
@@ -25,7 +26,6 @@ export interface LinkPluginOptions {
25
26
  insertLink?: string;
26
27
  removeLink?: string;
27
28
  };
28
- urlRegex?: RegExp;
29
29
  }
30
30
 
31
31
  const defaultShortcuts = {
@@ -33,8 +33,6 @@ const defaultShortcuts = {
33
33
  removeLink: "Mod-Shift-k",
34
34
  };
35
35
 
36
- const URL_RE = /^(?:https?:\/\/|mailto:|tel:)\S+$/;
37
-
38
36
  const linkHighlight = HighlightStyle.define([
39
37
  { tag: tags.link, color: "var(--lp-color-accent)" },
40
38
  { tag: tags.url, color: "var(--lp-color-accent)" },
@@ -42,25 +40,10 @@ const linkHighlight = HighlightStyle.define([
42
40
 
43
41
  export function linkPlugin(options?: LinkPluginOptions): Extension {
44
42
  const keys = { ...defaultShortcuts, ...options?.shortcuts };
45
- const urlRegex = options?.urlRegex ?? URL_RE;
46
43
 
47
44
  return [
48
45
  syntaxHighlighting(linkHighlight),
49
- EditorView.domEventHandlers({
50
- paste(event, view) {
51
- const range = view.state.selection.main;
52
- if (range.empty) return false;
53
- if (isInsideCodeBlock(view)) return false;
54
-
55
- const text = event.clipboardData?.getData("text/plain")?.trim();
56
- if (!text || !urlRegex.test(text)) return false;
57
-
58
- event.preventDefault();
59
- const label = view.state.sliceDoc(range.from, range.to);
60
- insertLink(view, range.from, range.to, label, text);
61
- return true;
62
- },
63
- }),
46
+ pasteURLAsLink,
64
47
  shortcutRegistry.of([
65
48
  { key: keys.insertLink, action: "insertLink", plugin: "link" },
66
49
  { key: keys.removeLink, action: "removeLink", plugin: "link" },
@@ -121,4 +104,4 @@ export function linkPlugin(options?: LinkPluginOptions): Extension {
121
104
  }
122
105
 
123
106
  // Re-export for convenience
124
- export { insertLink, findLinkAtCursor };
107
+ export { findLinkAtCursor, insertLink };
@@ -53,16 +53,16 @@ const inlineCommands: Record<Exclude<InlineAction, "link">, (view: EditorView) =
53
53
  const heading1Cmd = toggleHeading(1);
54
54
  const heading2Cmd = toggleHeading(2);
55
55
  const heading3Cmd = toggleHeading(3);
56
- const ulCmd = toggleListKind("ul");
57
56
  const olCmd = toggleListKind("ol");
57
+ const ulCmd = toggleListKind("ul");
58
58
  const taskCmd = toggleListKind("task");
59
59
 
60
60
  const blockCommands: Record<Exclude<BlockAction, "codeblock">, (view: EditorView) => void> = {
61
61
  heading1: heading1Cmd,
62
62
  heading2: heading2Cmd,
63
63
  heading3: heading3Cmd,
64
- orderedList: ulCmd,
65
- unorderedList: olCmd,
64
+ orderedList: olCmd,
65
+ unorderedList: ulCmd,
66
66
  taskList: taskCmd,
67
67
  blockquote: toggleBlockquote,
68
68
  };