@kungal/editor-core 0.33.0 → 0.35.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/CHANGELOG.md +73 -0
- package/dist/chunk-IIVFLZTN.js +50 -0
- package/dist/chunk-IIVFLZTN.js.map +1 -0
- package/dist/index.cjs +21 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -2
- package/dist/index.d.ts +19 -2
- package/dist/index.js +1 -1
- package/dist/preset/index.cjs +23 -1
- package/dist/preset/index.cjs.map +1 -1
- package/dist/preset/index.d.cts +10 -2
- package/dist/preset/index.d.ts +10 -2
- package/dist/preset/index.js +2 -2
- package/dist/preset/index.js.map +1 -1
- package/dist/{types-DqQwYYJ1.d.cts → types-f8Szrgvw.d.cts} +8 -5
- package/dist/{types-DqQwYYJ1.d.ts → types-f8Szrgvw.d.ts} +8 -5
- package/package.json +1 -1
- package/dist/chunk-PT42ZQGO.js +0 -30
- package/dist/chunk-PT42ZQGO.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,78 @@
|
|
|
1
1
|
# @kungal/editor-core
|
|
2
2
|
|
|
3
|
+
## 0.35.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- c90e1b9: fix(link): give a typed URL the scheme it needs, in ONE place
|
|
8
|
+
|
|
9
|
+
Typing `www.kungal.com/topic/1` into any link input produced a **relative**
|
|
10
|
+
href: with no scheme the browser resolves it against the current page, so the
|
|
11
|
+
link went to `<origin>/<current/path>/www.kungal.com/topic/1` — dead, and dead
|
|
12
|
+
in a way the author can't see while writing.
|
|
13
|
+
|
|
14
|
+
`insertLinkCommand` now normalizes its `href`, and since every link entry point
|
|
15
|
+
(the selection bubble's input, the headless toolbar's URL panel, the KunUI
|
|
16
|
+
toolbar's popover, and a host's own `linkPrompt` adapter) dispatches that one
|
|
17
|
+
command, all of them are fixed at once — a host must not re-implement this.
|
|
18
|
+
|
|
19
|
+
- `www.kungal.com/topic/1` → `https://www.kungal.com/topic/1` (https, not http:
|
|
20
|
+
an http-only site redirects, an https-only site doesn't). Ports, queries,
|
|
21
|
+
fragments and bare IPv4 hosts included.
|
|
22
|
+
- `me@kungal.com` → `mailto:me@kungal.com` (`https://` would read the address as
|
|
23
|
+
userinfo and go nowhere).
|
|
24
|
+
- Untouched: anything with a scheme — `kungal-user:` / `kungal-reply:` included
|
|
25
|
+
— and anything explicitly relative (`/x`, `./x`, `../x`, `#x`, `?x`, `//host`).
|
|
26
|
+
- The one ambiguous input is a lone dotted word: `readme.md` looks exactly like
|
|
27
|
+
a hostname (`.md` is a TLD), so it gets `https://`. Write `./readme.md` when a
|
|
28
|
+
relative file is what you mean.
|
|
29
|
+
|
|
30
|
+
The rule is also exported as `normalizeLinkHref(input)` from the light,
|
|
31
|
+
zero-dependency `@kungal/editor-core` entry, so a server can normalize legacy
|
|
32
|
+
content with the exact same logic.
|
|
33
|
+
|
|
34
|
+
Along with it, the three URL inputs move from `type="url"` to `type="text"` +
|
|
35
|
+
`inputmode="url"`. Native URL validation rejects exactly the schemeless input
|
|
36
|
+
this change exists to accept — in the KunUI toolbar's popover, which submits a
|
|
37
|
+
real `<form>`, pressing Enter on `www.kungal.com/topic/1` did nothing at all.
|
|
38
|
+
The mobile keyboard hint is kept.
|
|
39
|
+
|
|
40
|
+
## 0.34.0
|
|
41
|
+
|
|
42
|
+
### Minor Changes
|
|
43
|
+
|
|
44
|
+
- ab7fbc2: No native `prompt` left: the headless toolbar gets a link URL panel too
|
|
45
|
+
|
|
46
|
+
0.33.0 gave the selection bubble an inline URL input; the fixed (headless)
|
|
47
|
+
toolbar still popped `window.prompt`. It now opens a small panel under the link
|
|
48
|
+
button — Enter applies, Esc cancels, clicking outside closes, and the editor
|
|
49
|
+
gets focus back. Same behaviour as the bubble otherwise: an existing href is
|
|
50
|
+
prefilled (and selected), and with nothing selected the URL is inserted as
|
|
51
|
+
linked text. A panel rather than an in-place swap because a fixed toolbar row
|
|
52
|
+
that rearranges itself is jarring; the bubble is already a floating layer, so
|
|
53
|
+
there the swap is the right shape.
|
|
54
|
+
|
|
55
|
+
**Breaking (CSS hook, one version old):** `.kun-editor__bubble-input` →
|
|
56
|
+
`.kun-editor__link-input`. Both entry points render the same input, so the hook
|
|
57
|
+
is named after the thing, not the place. Hosts importing
|
|
58
|
+
`@kungal/editor-nuxt/editor.css` need no change; a copied stylesheet wants the
|
|
59
|
+
rename plus the two new hooks:
|
|
60
|
+
|
|
61
|
+
```css
|
|
62
|
+
.kun-editor__link {
|
|
63
|
+
position: relative;
|
|
64
|
+
} /* structural */
|
|
65
|
+
.kun-editor__link-panel {
|
|
66
|
+
position: absolute;
|
|
67
|
+
} /* structural */
|
|
68
|
+
.kun-editor__link-input {
|
|
69
|
+
/* shared by the toolbar panel and the bubble */
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`linkPrompt` keeps taking precedence over all three built-in entries (bubble
|
|
74
|
+
input, toolbar panel, KunUI popover), so a host modal is still one override.
|
|
75
|
+
|
|
3
76
|
## 0.33.0
|
|
4
77
|
|
|
5
78
|
### Minor Changes
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// src/outline.ts
|
|
2
|
+
var FENCE = /^\s{0,3}(?:```|~~~)/;
|
|
3
|
+
var ATX = /^ {0,3}(#{1,6})\s+(.+?)\s*#*\s*$/;
|
|
4
|
+
var parseHeadings = (markdown) => {
|
|
5
|
+
const out = [];
|
|
6
|
+
let inFence = false;
|
|
7
|
+
for (const line of markdown.split("\n")) {
|
|
8
|
+
if (FENCE.test(line)) {
|
|
9
|
+
inFence = !inFence;
|
|
10
|
+
continue;
|
|
11
|
+
}
|
|
12
|
+
if (inFence) {
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
const m = line.match(ATX);
|
|
16
|
+
if (m) {
|
|
17
|
+
out.push({ level: m[1].length, text: m[2].trim() });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return out;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// src/href.ts
|
|
24
|
+
var HAS_SCHEME = /^[a-z][a-z0-9+-]*:/i;
|
|
25
|
+
var IS_RELATIVE = /^[/?#.]/;
|
|
26
|
+
var IS_EMAIL = /^[^\s@/]+@[^\s@/]+\.[a-z]{2,}$/i;
|
|
27
|
+
var STARTS_WITH_HOST = /^[^\s/?#@]+\.[a-z]{2,}(?=$|[/?#:])/i;
|
|
28
|
+
var STARTS_WITH_IPV4 = /^\d{1,3}(\.\d{1,3}){3}(?=$|[/?#:])/;
|
|
29
|
+
var normalizeLinkHref = (input) => {
|
|
30
|
+
const href = input.trim();
|
|
31
|
+
if (!href || HAS_SCHEME.test(href) || IS_RELATIVE.test(href)) {
|
|
32
|
+
return href;
|
|
33
|
+
}
|
|
34
|
+
if (IS_EMAIL.test(href)) {
|
|
35
|
+
return `mailto:${href}`;
|
|
36
|
+
}
|
|
37
|
+
if (STARTS_WITH_HOST.test(href) || STARTS_WITH_IPV4.test(href)) {
|
|
38
|
+
return `https://${href}`;
|
|
39
|
+
}
|
|
40
|
+
return href;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// src/index.ts
|
|
44
|
+
var MENTION_SCHEME = "kungal-user:";
|
|
45
|
+
var QUOTE_SCHEME = "kungal-reply:";
|
|
46
|
+
var KUN_EDITOR_CORE_VERSION = "0.0.0";
|
|
47
|
+
|
|
48
|
+
export { KUN_EDITOR_CORE_VERSION, MENTION_SCHEME, QUOTE_SCHEME, normalizeLinkHref, parseHeadings };
|
|
49
|
+
//# sourceMappingURL=chunk-IIVFLZTN.js.map
|
|
50
|
+
//# sourceMappingURL=chunk-IIVFLZTN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/outline.ts","../src/href.ts","../src/index.ts"],"names":[],"mappings":";AAgBA,IAAM,KAAA,GAAQ,qBAAA;AACd,IAAM,GAAA,GAAM,kCAAA;AAGL,IAAM,aAAA,GAAgB,CAAC,QAAA,KAAmC;AAC/D,EAAA,MAAM,MAAoB,EAAC;AAC3B,EAAA,IAAI,OAAA,GAAU,KAAA;AACd,EAAA,KAAA,MAAW,IAAA,IAAQ,QAAA,CAAS,KAAA,CAAM,IAAI,CAAA,EAAG;AACvC,IAAA,IAAI,KAAA,CAAM,IAAA,CAAK,IAAI,CAAA,EAAG;AACpB,MAAA,OAAA,GAAU,CAAC,OAAA;AACX,MAAA;AAAA,IACF;AACA,IAAA,IAAI,OAAA,EAAS;AACX,MAAA;AAAA,IACF;AACA,IAAA,MAAM,CAAA,GAAI,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA;AACxB,IAAA,IAAI,CAAA,EAAG;AACL,MAAA,GAAA,CAAI,IAAA,CAAK,EAAE,KAAA,EAAO,CAAA,CAAE,CAAC,CAAA,CAAG,MAAA,EAAQ,IAAA,EAAM,CAAA,CAAE,CAAC,CAAA,CAAG,IAAA,IAAQ,CAAA;AAAA,IACtD;AAAA,EACF;AACA,EAAA,OAAO,GAAA;AACT;;;ACnBA,IAAM,UAAA,GAAa,qBAAA;AAOnB,IAAM,WAAA,GAAc,SAAA;AAGpB,IAAM,QAAA,GAAW,iCAAA;AAIjB,IAAM,gBAAA,GAAmB,qCAAA;AAGzB,IAAM,gBAAA,GAAmB,oCAAA;AAiBlB,IAAM,iBAAA,GAAoB,CAAC,KAAA,KAA0B;AAC1D,EAAA,MAAM,IAAA,GAAO,MAAM,IAAA,EAAK;AACxB,EAAA,IAAI,CAAC,QAAQ,UAAA,CAAW,IAAA,CAAK,IAAI,CAAA,IAAK,WAAA,CAAY,IAAA,CAAK,IAAI,CAAA,EAAG;AAC5D,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,IAAI,QAAA,CAAS,IAAA,CAAK,IAAI,CAAA,EAAG;AACvB,IAAA,OAAO,UAAU,IAAI,CAAA,CAAA;AAAA,EACvB;AACA,EAAA,IAAI,iBAAiB,IAAA,CAAK,IAAI,KAAK,gBAAA,CAAiB,IAAA,CAAK,IAAI,CAAA,EAAG;AAC9D,IAAA,OAAO,WAAW,IAAI,CAAA,CAAA;AAAA,EACxB;AACA,EAAA,OAAO,IAAA;AACT;;;AC5CO,IAAM,cAAA,GAAiB;AAMvB,IAAM,YAAA,GAAe;AAErB,IAAM,uBAAA,GAA0B","file":"chunk-IIVFLZTN.js","sourcesContent":["// Heading outline (table-of-contents) parsing — pure, zero-dep, so it lives in\n// the light main entry (a host, or the server, can build a TOC without pulling in\n// @milkdown/kit). Parses ATX headings (`#`…`######`) — what the toolbar produces\n// — and skips fenced code blocks so a `# comment` inside ``` isn't mistaken for a\n// heading. Setext headings (underlined) are intentionally not parsed.\n//\n// The result is an ordered list; a UI renders it and, on click, calls the\n// editor's `scrollToHeading(index)` with the item's array index.\n\nexport interface KunHeading {\n /** Heading level 1–6. */\n level: number\n /** The heading's text (trailing `#` closers stripped). */\n text: string\n}\n\nconst FENCE = /^\\s{0,3}(?:```|~~~)/\nconst ATX = /^ {0,3}(#{1,6})\\s+(.+?)\\s*#*\\s*$/\n\n/** Parse the ordered ATX-heading outline from a markdown string. */\nexport const parseHeadings = (markdown: string): KunHeading[] => {\n const out: KunHeading[] = []\n let inFence = false\n for (const line of markdown.split('\\n')) {\n if (FENCE.test(line)) {\n inFence = !inFence\n continue\n }\n if (inFence) {\n continue\n }\n const m = line.match(ATX)\n if (m) {\n out.push({ level: m[1]!.length, text: m[2]!.trim() })\n }\n }\n return out\n}\n","// Link URL normalization — the ONE place a typed URL becomes a real URL.\n//\n// `www.kungal.com/topic/1` is not a URL: with no scheme the browser resolves it\n// against the current page, so the link silently lands on\n// `<origin>/<current/path>/www.kungal.com/topic/1` — dead, and dead in a way the\n// author cannot see while writing. Every link entry point (the selection\n// bubble's input, the headless toolbar's panel, the KunUI popover, and a host's\n// own `linkPrompt`) dispatches `insertLinkCommand`, so normalizing inside that\n// command covers all of them at once — a host must never re-implement this.\n//\n// It lives in the LIGHT entry (pure string work, zero deps) so a server can\n// import it too, e.g. to normalize legacy content on the way in.\n\n/**\n * A `scheme:` prefix. RFC 3986 allows `.` in a scheme; this deliberately does\n * NOT, so `www.kungal.com:8080/x` reads as host:port instead of as a scheme.\n * (`localhost:3000` still reads as a scheme — genuinely ambiguous, left alone.)\n */\nconst HAS_SCHEME = /^[a-z][a-z0-9+-]*:/i\n\n/**\n * Deliberately relative, so it must survive untouched: `/abs`, `?q`, `#anchor`,\n * `./rel`, `../up`, and protocol-relative `//host` (already valid in a browser).\n * This is also the escape hatch — write `./readme.md` for a relative file.\n */\nconst IS_RELATIVE = /^[/?#.]/\n\n/** A plain email address. `https://` would read it as userinfo and go nowhere. */\nconst IS_EMAIL = /^[^\\s@/]+@[^\\s@/]+\\.[a-z]{2,}$/i\n\n/** Starts with a hostname: a dotted label plus an alphabetic TLD, then end or\n * one of `/ ? # :`. Excludes `@` so a userinfo URL is never invented. */\nconst STARTS_WITH_HOST = /^[^\\s/?#@]+\\.[a-z]{2,}(?=$|[/?#:])/i\n\n/** A bare IPv4 host (`10.0.0.5:8080/x`) — dotted, but it has no TLD to match. */\nconst STARTS_WITH_IPV4 = /^\\d{1,3}(\\.\\d{1,3}){3}(?=$|[/?#:])/\n\n/**\n * Give a user-typed link URL a scheme, so it points where the author meant.\n *\n * - `www.kungal.com/topic/1` → `https://www.kungal.com/topic/1` (https, not\n * http: an http-only site redirects, an https-only site does not).\n * - `me@kungal.com` → `mailto:me@kungal.com`.\n * - Anything already carrying a scheme is returned as typed — including\n * `kungal-user:` / `kungal-reply:` (see MENTION_SCHEME, QUOTE_SCHEME).\n * - Anything explicitly relative (`/x`, `./x`, `#x`, `?x`, `//host`) is left\n * alone, as is anything that doesn't look like a host at all (`draft`).\n *\n * The one ambiguous input is a lone dotted word: `readme.md` looks exactly like\n * a hostname (`.md` IS a TLD), so it gets `https://`. Write `./readme.md` when\n * a relative file is what you mean.\n */\nexport const normalizeLinkHref = (input: string): string => {\n const href = input.trim()\n if (!href || HAS_SCHEME.test(href) || IS_RELATIVE.test(href)) {\n return href\n }\n if (IS_EMAIL.test(href)) {\n return `mailto:${href}`\n }\n if (STARTS_WITH_HOST.test(href) || STARTS_WITH_IPV4.test(href)) {\n return `https://${href}`\n }\n return href\n}\n","// @kungal/editor-core — public entry.\n//\n// STATUS: scaffold. The adapter contracts (the stable public surface) are\n// defined and exported now; the Milkdown plugin ports land incrementally per\n// docs/architecture.md § migration. Consumers should code against the types\n// below — those are the contract that will not churn as plugins move over.\n\nexport * from './types'\n\n// Heading outline (TOC) parsing — pure, so it's here in the light entry.\nexport * from './outline'\n\n// Link URL normalization (`www.a.com/x` → `https://www.a.com/x`). Applied by\n// `insertLinkCommand` for every link entry point; exported here so a server can\n// normalize legacy content with the exact same rules. Pure. See ./href.\nexport * from './href'\n\n// Markdown scheme used to encode an @mention as a plain link the server can\n// render + parse: `[@name](kungal-user:<id>)`. Lives here (not the plugin) so\n// hosts and the server can share the exact string. See ./plugins/mention.\nexport const MENTION_SCHEME = 'kungal-user:'\n\n// Markdown scheme for an inline reference (reply quote): `[label](kungal-reply:<refId>)`.\n// Like MENTION_SCHEME, shared with the server renderer so both agree on the\n// exact string. The reference is opaque here — the host decides what `refId` /\n// `label` mean (see docs/architecture.md § the reply-quote question, option 1).\nexport const QUOTE_SCHEME = 'kungal-reply:'\n\nexport const KUN_EDITOR_CORE_VERSION = '0.0.0'\n\n// ── The Milkdown plugins live in the `./preset` subpath ──────────────────────\n// This main entry stays light on purpose: types + MENTION_SCHEME, ZERO runtime\n// deps, so the server (which only needs the @mention scheme string) can import\n// it without installing @milkdown/kit / katex / codemirror.\n//\n// The composed Milkdown bundle and the individual plugin factories are exported\n// from `@kungal/editor-core/preset` (they pull in the peer deps):\n//\n// import { createKunEditorPlugins } from '@kungal/editor-core/preset'\n//\n// P1 landed (docs/architecture.md § migration): spoiler, katex, code-block,\n// stop-link — each a factory (createXxxPlugin), never a host-bound singleton.\n// P2 adds the adapter-driven plugins (upload / mention / sticker).\n"]}
|
package/dist/index.cjs
CHANGED
|
@@ -22,6 +22,26 @@ var parseHeadings = (markdown) => {
|
|
|
22
22
|
return out;
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
+
// src/href.ts
|
|
26
|
+
var HAS_SCHEME = /^[a-z][a-z0-9+-]*:/i;
|
|
27
|
+
var IS_RELATIVE = /^[/?#.]/;
|
|
28
|
+
var IS_EMAIL = /^[^\s@/]+@[^\s@/]+\.[a-z]{2,}$/i;
|
|
29
|
+
var STARTS_WITH_HOST = /^[^\s/?#@]+\.[a-z]{2,}(?=$|[/?#:])/i;
|
|
30
|
+
var STARTS_WITH_IPV4 = /^\d{1,3}(\.\d{1,3}){3}(?=$|[/?#:])/;
|
|
31
|
+
var normalizeLinkHref = (input) => {
|
|
32
|
+
const href = input.trim();
|
|
33
|
+
if (!href || HAS_SCHEME.test(href) || IS_RELATIVE.test(href)) {
|
|
34
|
+
return href;
|
|
35
|
+
}
|
|
36
|
+
if (IS_EMAIL.test(href)) {
|
|
37
|
+
return `mailto:${href}`;
|
|
38
|
+
}
|
|
39
|
+
if (STARTS_WITH_HOST.test(href) || STARTS_WITH_IPV4.test(href)) {
|
|
40
|
+
return `https://${href}`;
|
|
41
|
+
}
|
|
42
|
+
return href;
|
|
43
|
+
};
|
|
44
|
+
|
|
25
45
|
// src/index.ts
|
|
26
46
|
var MENTION_SCHEME = "kungal-user:";
|
|
27
47
|
var QUOTE_SCHEME = "kungal-reply:";
|
|
@@ -30,6 +50,7 @@ var KUN_EDITOR_CORE_VERSION = "0.0.0";
|
|
|
30
50
|
exports.KUN_EDITOR_CORE_VERSION = KUN_EDITOR_CORE_VERSION;
|
|
31
51
|
exports.MENTION_SCHEME = MENTION_SCHEME;
|
|
32
52
|
exports.QUOTE_SCHEME = QUOTE_SCHEME;
|
|
53
|
+
exports.normalizeLinkHref = normalizeLinkHref;
|
|
33
54
|
exports.parseHeadings = parseHeadings;
|
|
34
55
|
//# sourceMappingURL=index.cjs.map
|
|
35
56
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/outline.ts","../src/index.ts"],"names":[],"mappings":";;;AAgBA,IAAM,KAAA,GAAQ,qBAAA;AACd,IAAM,GAAA,GAAM,kCAAA;AAGL,IAAM,aAAA,GAAgB,CAAC,QAAA,KAAmC;AAC/D,EAAA,MAAM,MAAoB,EAAC;AAC3B,EAAA,IAAI,OAAA,GAAU,KAAA;AACd,EAAA,KAAA,MAAW,IAAA,IAAQ,QAAA,CAAS,KAAA,CAAM,IAAI,CAAA,EAAG;AACvC,IAAA,IAAI,KAAA,CAAM,IAAA,CAAK,IAAI,CAAA,EAAG;AACpB,MAAA,OAAA,GAAU,CAAC,OAAA;AACX,MAAA;AAAA,IACF;AACA,IAAA,IAAI,OAAA,EAAS;AACX,MAAA;AAAA,IACF;AACA,IAAA,MAAM,CAAA,GAAI,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA;AACxB,IAAA,IAAI,CAAA,EAAG;AACL,MAAA,GAAA,CAAI,IAAA,CAAK,EAAE,KAAA,EAAO,CAAA,CAAE,CAAC,CAAA,CAAG,MAAA,EAAQ,IAAA,EAAM,CAAA,CAAE,CAAC,CAAA,CAAG,IAAA,IAAQ,CAAA;AAAA,IACtD;AAAA,EACF;AACA,EAAA,OAAO,GAAA;AACT;;;
|
|
1
|
+
{"version":3,"sources":["../src/outline.ts","../src/href.ts","../src/index.ts"],"names":[],"mappings":";;;AAgBA,IAAM,KAAA,GAAQ,qBAAA;AACd,IAAM,GAAA,GAAM,kCAAA;AAGL,IAAM,aAAA,GAAgB,CAAC,QAAA,KAAmC;AAC/D,EAAA,MAAM,MAAoB,EAAC;AAC3B,EAAA,IAAI,OAAA,GAAU,KAAA;AACd,EAAA,KAAA,MAAW,IAAA,IAAQ,QAAA,CAAS,KAAA,CAAM,IAAI,CAAA,EAAG;AACvC,IAAA,IAAI,KAAA,CAAM,IAAA,CAAK,IAAI,CAAA,EAAG;AACpB,MAAA,OAAA,GAAU,CAAC,OAAA;AACX,MAAA;AAAA,IACF;AACA,IAAA,IAAI,OAAA,EAAS;AACX,MAAA;AAAA,IACF;AACA,IAAA,MAAM,CAAA,GAAI,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA;AACxB,IAAA,IAAI,CAAA,EAAG;AACL,MAAA,GAAA,CAAI,IAAA,CAAK,EAAE,KAAA,EAAO,CAAA,CAAE,CAAC,CAAA,CAAG,MAAA,EAAQ,IAAA,EAAM,CAAA,CAAE,CAAC,CAAA,CAAG,IAAA,IAAQ,CAAA;AAAA,IACtD;AAAA,EACF;AACA,EAAA,OAAO,GAAA;AACT;;;ACnBA,IAAM,UAAA,GAAa,qBAAA;AAOnB,IAAM,WAAA,GAAc,SAAA;AAGpB,IAAM,QAAA,GAAW,iCAAA;AAIjB,IAAM,gBAAA,GAAmB,qCAAA;AAGzB,IAAM,gBAAA,GAAmB,oCAAA;AAiBlB,IAAM,iBAAA,GAAoB,CAAC,KAAA,KAA0B;AAC1D,EAAA,MAAM,IAAA,GAAO,MAAM,IAAA,EAAK;AACxB,EAAA,IAAI,CAAC,QAAQ,UAAA,CAAW,IAAA,CAAK,IAAI,CAAA,IAAK,WAAA,CAAY,IAAA,CAAK,IAAI,CAAA,EAAG;AAC5D,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,IAAI,QAAA,CAAS,IAAA,CAAK,IAAI,CAAA,EAAG;AACvB,IAAA,OAAO,UAAU,IAAI,CAAA,CAAA;AAAA,EACvB;AACA,EAAA,IAAI,iBAAiB,IAAA,CAAK,IAAI,KAAK,gBAAA,CAAiB,IAAA,CAAK,IAAI,CAAA,EAAG;AAC9D,IAAA,OAAO,WAAW,IAAI,CAAA,CAAA;AAAA,EACxB;AACA,EAAA,OAAO,IAAA;AACT;;;AC5CO,IAAM,cAAA,GAAiB;AAMvB,IAAM,YAAA,GAAe;AAErB,IAAM,uBAAA,GAA0B","file":"index.cjs","sourcesContent":["// Heading outline (table-of-contents) parsing — pure, zero-dep, so it lives in\n// the light main entry (a host, or the server, can build a TOC without pulling in\n// @milkdown/kit). Parses ATX headings (`#`…`######`) — what the toolbar produces\n// — and skips fenced code blocks so a `# comment` inside ``` isn't mistaken for a\n// heading. Setext headings (underlined) are intentionally not parsed.\n//\n// The result is an ordered list; a UI renders it and, on click, calls the\n// editor's `scrollToHeading(index)` with the item's array index.\n\nexport interface KunHeading {\n /** Heading level 1–6. */\n level: number\n /** The heading's text (trailing `#` closers stripped). */\n text: string\n}\n\nconst FENCE = /^\\s{0,3}(?:```|~~~)/\nconst ATX = /^ {0,3}(#{1,6})\\s+(.+?)\\s*#*\\s*$/\n\n/** Parse the ordered ATX-heading outline from a markdown string. */\nexport const parseHeadings = (markdown: string): KunHeading[] => {\n const out: KunHeading[] = []\n let inFence = false\n for (const line of markdown.split('\\n')) {\n if (FENCE.test(line)) {\n inFence = !inFence\n continue\n }\n if (inFence) {\n continue\n }\n const m = line.match(ATX)\n if (m) {\n out.push({ level: m[1]!.length, text: m[2]!.trim() })\n }\n }\n return out\n}\n","// Link URL normalization — the ONE place a typed URL becomes a real URL.\n//\n// `www.kungal.com/topic/1` is not a URL: with no scheme the browser resolves it\n// against the current page, so the link silently lands on\n// `<origin>/<current/path>/www.kungal.com/topic/1` — dead, and dead in a way the\n// author cannot see while writing. Every link entry point (the selection\n// bubble's input, the headless toolbar's panel, the KunUI popover, and a host's\n// own `linkPrompt`) dispatches `insertLinkCommand`, so normalizing inside that\n// command covers all of them at once — a host must never re-implement this.\n//\n// It lives in the LIGHT entry (pure string work, zero deps) so a server can\n// import it too, e.g. to normalize legacy content on the way in.\n\n/**\n * A `scheme:` prefix. RFC 3986 allows `.` in a scheme; this deliberately does\n * NOT, so `www.kungal.com:8080/x` reads as host:port instead of as a scheme.\n * (`localhost:3000` still reads as a scheme — genuinely ambiguous, left alone.)\n */\nconst HAS_SCHEME = /^[a-z][a-z0-9+-]*:/i\n\n/**\n * Deliberately relative, so it must survive untouched: `/abs`, `?q`, `#anchor`,\n * `./rel`, `../up`, and protocol-relative `//host` (already valid in a browser).\n * This is also the escape hatch — write `./readme.md` for a relative file.\n */\nconst IS_RELATIVE = /^[/?#.]/\n\n/** A plain email address. `https://` would read it as userinfo and go nowhere. */\nconst IS_EMAIL = /^[^\\s@/]+@[^\\s@/]+\\.[a-z]{2,}$/i\n\n/** Starts with a hostname: a dotted label plus an alphabetic TLD, then end or\n * one of `/ ? # :`. Excludes `@` so a userinfo URL is never invented. */\nconst STARTS_WITH_HOST = /^[^\\s/?#@]+\\.[a-z]{2,}(?=$|[/?#:])/i\n\n/** A bare IPv4 host (`10.0.0.5:8080/x`) — dotted, but it has no TLD to match. */\nconst STARTS_WITH_IPV4 = /^\\d{1,3}(\\.\\d{1,3}){3}(?=$|[/?#:])/\n\n/**\n * Give a user-typed link URL a scheme, so it points where the author meant.\n *\n * - `www.kungal.com/topic/1` → `https://www.kungal.com/topic/1` (https, not\n * http: an http-only site redirects, an https-only site does not).\n * - `me@kungal.com` → `mailto:me@kungal.com`.\n * - Anything already carrying a scheme is returned as typed — including\n * `kungal-user:` / `kungal-reply:` (see MENTION_SCHEME, QUOTE_SCHEME).\n * - Anything explicitly relative (`/x`, `./x`, `#x`, `?x`, `//host`) is left\n * alone, as is anything that doesn't look like a host at all (`draft`).\n *\n * The one ambiguous input is a lone dotted word: `readme.md` looks exactly like\n * a hostname (`.md` IS a TLD), so it gets `https://`. Write `./readme.md` when\n * a relative file is what you mean.\n */\nexport const normalizeLinkHref = (input: string): string => {\n const href = input.trim()\n if (!href || HAS_SCHEME.test(href) || IS_RELATIVE.test(href)) {\n return href\n }\n if (IS_EMAIL.test(href)) {\n return `mailto:${href}`\n }\n if (STARTS_WITH_HOST.test(href) || STARTS_WITH_IPV4.test(href)) {\n return `https://${href}`\n }\n return href\n}\n","// @kungal/editor-core — public entry.\n//\n// STATUS: scaffold. The adapter contracts (the stable public surface) are\n// defined and exported now; the Milkdown plugin ports land incrementally per\n// docs/architecture.md § migration. Consumers should code against the types\n// below — those are the contract that will not churn as plugins move over.\n\nexport * from './types'\n\n// Heading outline (TOC) parsing — pure, so it's here in the light entry.\nexport * from './outline'\n\n// Link URL normalization (`www.a.com/x` → `https://www.a.com/x`). Applied by\n// `insertLinkCommand` for every link entry point; exported here so a server can\n// normalize legacy content with the exact same rules. Pure. See ./href.\nexport * from './href'\n\n// Markdown scheme used to encode an @mention as a plain link the server can\n// render + parse: `[@name](kungal-user:<id>)`. Lives here (not the plugin) so\n// hosts and the server can share the exact string. See ./plugins/mention.\nexport const MENTION_SCHEME = 'kungal-user:'\n\n// Markdown scheme for an inline reference (reply quote): `[label](kungal-reply:<refId>)`.\n// Like MENTION_SCHEME, shared with the server renderer so both agree on the\n// exact string. The reference is opaque here — the host decides what `refId` /\n// `label` mean (see docs/architecture.md § the reply-quote question, option 1).\nexport const QUOTE_SCHEME = 'kungal-reply:'\n\nexport const KUN_EDITOR_CORE_VERSION = '0.0.0'\n\n// ── The Milkdown plugins live in the `./preset` subpath ──────────────────────\n// This main entry stays light on purpose: types + MENTION_SCHEME, ZERO runtime\n// deps, so the server (which only needs the @mention scheme string) can import\n// it without installing @milkdown/kit / katex / codemirror.\n//\n// The composed Milkdown bundle and the individual plugin factories are exported\n// from `@kungal/editor-core/preset` (they pull in the peer deps):\n//\n// import { createKunEditorPlugins } from '@kungal/editor-core/preset'\n//\n// P1 landed (docs/architecture.md § migration): spoiler, katex, code-block,\n// stop-link — each a factory (createXxxPlugin), never a host-bound singleton.\n// P2 adds the adapter-driven plugins (upload / mention / sticker).\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { K as KunEditorAdapters, a as KunEditorFeatures, b as KunEditorLocale, L as LinkPrompt, M as MentionUser, N as Notify, c as NotifyLevel, S as SearchMentionUsers, d as StickerItem, e as StickerPack, f as StickerSource, U as UploadImage } from './types-
|
|
1
|
+
export { K as KunEditorAdapters, a as KunEditorFeatures, b as KunEditorLocale, L as LinkPrompt, M as MentionUser, N as Notify, c as NotifyLevel, S as SearchMentionUsers, d as StickerItem, e as StickerPack, f as StickerSource, U as UploadImage } from './types-f8Szrgvw.cjs';
|
|
2
2
|
|
|
3
3
|
interface KunHeading {
|
|
4
4
|
/** Heading level 1–6. */
|
|
@@ -9,8 +9,25 @@ interface KunHeading {
|
|
|
9
9
|
/** Parse the ordered ATX-heading outline from a markdown string. */
|
|
10
10
|
declare const parseHeadings: (markdown: string) => KunHeading[];
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Give a user-typed link URL a scheme, so it points where the author meant.
|
|
14
|
+
*
|
|
15
|
+
* - `www.kungal.com/topic/1` → `https://www.kungal.com/topic/1` (https, not
|
|
16
|
+
* http: an http-only site redirects, an https-only site does not).
|
|
17
|
+
* - `me@kungal.com` → `mailto:me@kungal.com`.
|
|
18
|
+
* - Anything already carrying a scheme is returned as typed — including
|
|
19
|
+
* `kungal-user:` / `kungal-reply:` (see MENTION_SCHEME, QUOTE_SCHEME).
|
|
20
|
+
* - Anything explicitly relative (`/x`, `./x`, `#x`, `?x`, `//host`) is left
|
|
21
|
+
* alone, as is anything that doesn't look like a host at all (`draft`).
|
|
22
|
+
*
|
|
23
|
+
* The one ambiguous input is a lone dotted word: `readme.md` looks exactly like
|
|
24
|
+
* a hostname (`.md` IS a TLD), so it gets `https://`. Write `./readme.md` when
|
|
25
|
+
* a relative file is what you mean.
|
|
26
|
+
*/
|
|
27
|
+
declare const normalizeLinkHref: (input: string) => string;
|
|
28
|
+
|
|
12
29
|
declare const MENTION_SCHEME = "kungal-user:";
|
|
13
30
|
declare const QUOTE_SCHEME = "kungal-reply:";
|
|
14
31
|
declare const KUN_EDITOR_CORE_VERSION = "0.0.0";
|
|
15
32
|
|
|
16
|
-
export { KUN_EDITOR_CORE_VERSION, type KunHeading, MENTION_SCHEME, QUOTE_SCHEME, parseHeadings };
|
|
33
|
+
export { KUN_EDITOR_CORE_VERSION, type KunHeading, MENTION_SCHEME, QUOTE_SCHEME, normalizeLinkHref, parseHeadings };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { K as KunEditorAdapters, a as KunEditorFeatures, b as KunEditorLocale, L as LinkPrompt, M as MentionUser, N as Notify, c as NotifyLevel, S as SearchMentionUsers, d as StickerItem, e as StickerPack, f as StickerSource, U as UploadImage } from './types-
|
|
1
|
+
export { K as KunEditorAdapters, a as KunEditorFeatures, b as KunEditorLocale, L as LinkPrompt, M as MentionUser, N as Notify, c as NotifyLevel, S as SearchMentionUsers, d as StickerItem, e as StickerPack, f as StickerSource, U as UploadImage } from './types-f8Szrgvw.js';
|
|
2
2
|
|
|
3
3
|
interface KunHeading {
|
|
4
4
|
/** Heading level 1–6. */
|
|
@@ -9,8 +9,25 @@ interface KunHeading {
|
|
|
9
9
|
/** Parse the ordered ATX-heading outline from a markdown string. */
|
|
10
10
|
declare const parseHeadings: (markdown: string) => KunHeading[];
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Give a user-typed link URL a scheme, so it points where the author meant.
|
|
14
|
+
*
|
|
15
|
+
* - `www.kungal.com/topic/1` → `https://www.kungal.com/topic/1` (https, not
|
|
16
|
+
* http: an http-only site redirects, an https-only site does not).
|
|
17
|
+
* - `me@kungal.com` → `mailto:me@kungal.com`.
|
|
18
|
+
* - Anything already carrying a scheme is returned as typed — including
|
|
19
|
+
* `kungal-user:` / `kungal-reply:` (see MENTION_SCHEME, QUOTE_SCHEME).
|
|
20
|
+
* - Anything explicitly relative (`/x`, `./x`, `#x`, `?x`, `//host`) is left
|
|
21
|
+
* alone, as is anything that doesn't look like a host at all (`draft`).
|
|
22
|
+
*
|
|
23
|
+
* The one ambiguous input is a lone dotted word: `readme.md` looks exactly like
|
|
24
|
+
* a hostname (`.md` IS a TLD), so it gets `https://`. Write `./readme.md` when
|
|
25
|
+
* a relative file is what you mean.
|
|
26
|
+
*/
|
|
27
|
+
declare const normalizeLinkHref: (input: string) => string;
|
|
28
|
+
|
|
12
29
|
declare const MENTION_SCHEME = "kungal-user:";
|
|
13
30
|
declare const QUOTE_SCHEME = "kungal-reply:";
|
|
14
31
|
declare const KUN_EDITOR_CORE_VERSION = "0.0.0";
|
|
15
32
|
|
|
16
|
-
export { KUN_EDITOR_CORE_VERSION, type KunHeading, MENTION_SCHEME, QUOTE_SCHEME, parseHeadings };
|
|
33
|
+
export { KUN_EDITOR_CORE_VERSION, type KunHeading, MENTION_SCHEME, QUOTE_SCHEME, normalizeLinkHref, parseHeadings };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { KUN_EDITOR_CORE_VERSION, MENTION_SCHEME, QUOTE_SCHEME, parseHeadings } from './chunk-
|
|
1
|
+
export { KUN_EDITOR_CORE_VERSION, MENTION_SCHEME, QUOTE_SCHEME, normalizeLinkHref, parseHeadings } from './chunk-IIVFLZTN.js';
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
|
3
3
|
//# sourceMappingURL=index.js.map
|
package/dist/preset/index.cjs
CHANGED
|
@@ -218,10 +218,32 @@ var clearOrphanLink = utils.$prose(
|
|
|
218
218
|
})
|
|
219
219
|
);
|
|
220
220
|
var createStopLinkPlugin = () => [stopLinkCommand, linkCustomKeymap, clearOrphanLink].flat();
|
|
221
|
+
|
|
222
|
+
// src/href.ts
|
|
223
|
+
var HAS_SCHEME = /^[a-z][a-z0-9+-]*:/i;
|
|
224
|
+
var IS_RELATIVE = /^[/?#.]/;
|
|
225
|
+
var IS_EMAIL = /^[^\s@/]+@[^\s@/]+\.[a-z]{2,}$/i;
|
|
226
|
+
var STARTS_WITH_HOST = /^[^\s/?#@]+\.[a-z]{2,}(?=$|[/?#:])/i;
|
|
227
|
+
var STARTS_WITH_IPV4 = /^\d{1,3}(\.\d{1,3}){3}(?=$|[/?#:])/;
|
|
228
|
+
var normalizeLinkHref = (input) => {
|
|
229
|
+
const href = input.trim();
|
|
230
|
+
if (!href || HAS_SCHEME.test(href) || IS_RELATIVE.test(href)) {
|
|
231
|
+
return href;
|
|
232
|
+
}
|
|
233
|
+
if (IS_EMAIL.test(href)) {
|
|
234
|
+
return `mailto:${href}`;
|
|
235
|
+
}
|
|
236
|
+
if (STARTS_WITH_HOST.test(href) || STARTS_WITH_IPV4.test(href)) {
|
|
237
|
+
return `https://${href}`;
|
|
238
|
+
}
|
|
239
|
+
return href;
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
// src/plugins/link/index.ts
|
|
221
243
|
var insertLinkCommand = utils.$command(
|
|
222
244
|
"InsertKunLink",
|
|
223
245
|
(ctx) => (payload) => (state, dispatch) => {
|
|
224
|
-
const href = payload?.href
|
|
246
|
+
const href = normalizeLinkHref(payload?.href ?? "");
|
|
225
247
|
if (!href) {
|
|
226
248
|
return false;
|
|
227
249
|
}
|