@blankdotpage/cake 0.1.8 → 0.1.10
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/cake/clipboard.d.ts +1 -0
- package/dist/cake/clipboard.d.ts.map +1 -0
- package/dist/cake/clipboard.js +391 -0
- package/dist/cake/core/mapping/cursor-source-map.d.ts +1 -0
- package/dist/cake/core/mapping/cursor-source-map.d.ts.map +1 -0
- package/dist/cake/core/mapping/cursor-source-map.js +146 -0
- package/dist/cake/core/runtime.d.ts +1 -0
- package/dist/cake/core/runtime.d.ts.map +1 -0
- package/dist/cake/core/runtime.js +1758 -0
- package/dist/cake/core/types.d.ts +1 -0
- package/dist/cake/core/types.d.ts.map +1 -0
- package/dist/cake/core/types.js +1 -0
- package/dist/cake/dom/dom-map.d.ts +1 -0
- package/dist/cake/dom/dom-map.d.ts.map +1 -0
- package/dist/cake/dom/dom-map.js +151 -0
- package/dist/cake/dom/dom-selection.d.ts +1 -0
- package/dist/cake/dom/dom-selection.d.ts.map +1 -0
- package/dist/cake/dom/dom-selection.js +216 -0
- package/dist/cake/dom/render.d.ts +1 -0
- package/dist/cake/dom/render.d.ts.map +1 -0
- package/dist/cake/dom/render.js +470 -0
- package/dist/cake/dom/types.d.ts +1 -0
- package/dist/cake/dom/types.d.ts.map +1 -0
- package/dist/cake/dom/types.js +1 -0
- package/dist/cake/engine/cake-engine.d.ts +1 -0
- package/dist/cake/engine/cake-engine.d.ts.map +1 -0
- package/dist/cake/engine/cake-engine.js +3589 -0
- package/dist/cake/engine/selection/selection-geometry-dom.d.ts +1 -0
- package/dist/cake/engine/selection/selection-geometry-dom.d.ts.map +1 -0
- package/dist/cake/engine/selection/selection-geometry-dom.js +302 -0
- package/dist/cake/engine/selection/selection-geometry.d.ts +1 -0
- package/dist/cake/engine/selection/selection-geometry.d.ts.map +1 -0
- package/dist/cake/engine/selection/selection-geometry.js +158 -0
- package/dist/cake/engine/selection/selection-layout-dom.d.ts +1 -0
- package/dist/cake/engine/selection/selection-layout-dom.d.ts.map +1 -0
- package/dist/cake/engine/selection/selection-layout-dom.js +781 -0
- package/dist/cake/engine/selection/selection-layout.d.ts +1 -0
- package/dist/cake/engine/selection/selection-layout.d.ts.map +1 -0
- package/dist/cake/engine/selection/selection-layout.js +128 -0
- package/dist/cake/engine/selection/selection-navigation.d.ts +1 -0
- package/dist/cake/engine/selection/selection-navigation.d.ts.map +1 -0
- package/dist/cake/engine/selection/selection-navigation.js +229 -0
- package/dist/cake/engine/selection/visible-text.d.ts +1 -0
- package/dist/cake/engine/selection/visible-text.d.ts.map +1 -0
- package/dist/cake/engine/selection/visible-text.js +66 -0
- package/dist/cake/extensions/blockquote/blockquote.d.ts +1 -0
- package/dist/cake/extensions/blockquote/blockquote.d.ts.map +1 -0
- package/dist/cake/extensions/blockquote/blockquote.js +177 -0
- package/dist/cake/extensions/blockquote/index.d.ts +2 -0
- package/dist/cake/extensions/blockquote/index.d.ts.map +1 -0
- package/dist/cake/extensions/blockquote/index.js +1 -0
- package/dist/cake/extensions/bold/bold.d.ts +1 -0
- package/dist/cake/extensions/bold/bold.d.ts.map +1 -0
- package/dist/cake/extensions/bold/bold.js +113 -0
- package/dist/cake/extensions/bold/index.d.ts +2 -0
- package/dist/cake/extensions/bold/index.d.ts.map +1 -0
- package/dist/cake/extensions/bold/index.js +1 -0
- package/dist/cake/extensions/bundles.d.ts +1 -0
- package/dist/cake/extensions/bundles.d.ts.map +1 -0
- package/dist/cake/extensions/bundles.js +12 -0
- package/dist/cake/extensions/combined-emphasis/combined-emphasis.d.ts +1 -0
- package/dist/cake/extensions/combined-emphasis/combined-emphasis.d.ts.map +1 -0
- package/dist/cake/extensions/combined-emphasis/combined-emphasis.js +42 -0
- package/dist/cake/extensions/combined-emphasis/index.d.ts +2 -0
- package/dist/cake/extensions/combined-emphasis/index.d.ts.map +1 -0
- package/dist/cake/extensions/combined-emphasis/index.js +1 -0
- package/dist/cake/extensions/heading/heading.d.ts +1 -0
- package/dist/cake/extensions/heading/heading.d.ts.map +1 -0
- package/dist/cake/extensions/heading/heading.js +337 -0
- package/dist/cake/extensions/heading/index.d.ts +2 -0
- package/dist/cake/extensions/heading/index.d.ts.map +1 -0
- package/dist/cake/extensions/heading/index.js +1 -0
- package/dist/cake/extensions/image/image.d.ts +1 -0
- package/dist/cake/extensions/image/image.d.ts.map +1 -0
- package/dist/cake/extensions/image/image.js +120 -0
- package/dist/cake/extensions/image/index.d.ts +2 -0
- package/dist/cake/extensions/image/index.d.ts.map +1 -0
- package/dist/cake/extensions/image/index.js +1 -0
- package/dist/cake/extensions/index.d.ts +2 -2
- package/dist/cake/extensions/index.d.ts.map +1 -0
- package/dist/cake/extensions/index.js +25 -0
- package/dist/cake/extensions/italic/index.d.ts +2 -0
- package/dist/cake/extensions/italic/index.d.ts.map +1 -0
- package/dist/cake/extensions/italic/index.js +1 -0
- package/dist/cake/extensions/italic/italic.d.ts +1 -0
- package/dist/cake/extensions/italic/italic.d.ts.map +1 -0
- package/dist/cake/extensions/italic/italic.js +91 -0
- package/dist/cake/extensions/link/index.d.ts +2 -0
- package/dist/cake/extensions/link/index.d.ts.map +1 -0
- package/dist/cake/extensions/link/index.js +1 -0
- package/dist/cake/extensions/link/link-popover.d.ts +1 -0
- package/dist/cake/extensions/link/link-popover.d.ts.map +1 -0
- package/dist/cake/extensions/link/link-popover.js +205 -0
- package/dist/cake/extensions/link/link.d.ts +1 -0
- package/dist/cake/extensions/link/link.d.ts.map +1 -0
- package/dist/cake/extensions/link/link.js +202 -0
- package/dist/cake/extensions/list/index.d.ts +2 -0
- package/dist/cake/extensions/list/index.d.ts.map +1 -0
- package/dist/cake/extensions/list/index.js +1 -0
- package/dist/cake/extensions/list/list-ast.d.ts +1 -0
- package/dist/cake/extensions/list/list-ast.d.ts.map +1 -0
- package/dist/cake/extensions/list/list-ast.js +248 -0
- package/dist/cake/extensions/list/list.d.ts +1 -0
- package/dist/cake/extensions/list/list.d.ts.map +1 -0
- package/dist/cake/extensions/list/list.js +859 -0
- package/dist/cake/extensions/scrollbar/index.d.ts +1 -0
- package/dist/cake/extensions/scrollbar/index.d.ts.map +1 -0
- package/dist/cake/extensions/scrollbar/index.js +216 -0
- package/dist/cake/extensions/strikethrough/index.d.ts +2 -0
- package/dist/cake/extensions/strikethrough/index.d.ts.map +1 -0
- package/dist/cake/extensions/strikethrough/index.js +1 -0
- package/dist/cake/extensions/strikethrough/strikethrough.d.ts +1 -0
- package/dist/cake/extensions/strikethrough/strikethrough.d.ts.map +1 -0
- package/dist/cake/extensions/strikethrough/strikethrough.js +84 -0
- package/dist/cake/extensions/types.d.ts +1 -0
- package/dist/cake/extensions/types.d.ts.map +1 -0
- package/dist/cake/extensions/types.js +1 -0
- package/dist/cake/index.d.ts +1 -0
- package/dist/cake/index.d.ts.map +1 -0
- package/dist/cake/index.js +1 -0
- package/dist/cake/react/CakeEditor.d.ts +1 -0
- package/dist/cake/react/CakeEditor.d.ts.map +1 -0
- package/dist/cake/react/CakeEditor.js +233 -0
- package/dist/cake/shared/platform.d.ts +1 -0
- package/dist/cake/shared/platform.d.ts.map +1 -0
- package/dist/cake/shared/platform.js +19 -0
- package/dist/cake/shared/segmenter.d.ts +1 -0
- package/dist/cake/shared/segmenter.d.ts.map +1 -0
- package/dist/cake/shared/segmenter.js +46 -0
- package/dist/cake/shared/url.d.ts +1 -0
- package/dist/cake/shared/url.d.ts.map +1 -0
- package/dist/cake/shared/url.js +37 -0
- package/dist/cake/shared/word-break.d.ts +1 -0
- package/dist/cake/shared/word-break.d.ts.map +1 -0
- package/dist/cake/shared/word-break.js +178 -0
- package/dist/cake/test/harness.d.ts +1 -0
- package/dist/cake/test/harness.d.ts.map +1 -0
- package/dist/cake/test/harness.js +504 -0
- package/dist/codemirror/markdown-commands.d.ts +1 -0
- package/dist/codemirror/markdown-commands.d.ts.map +1 -0
- package/dist/codemirror/markdown-commands.js +532 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -11793
- package/package.json +8 -6
- package/dist/cake/extensions/pipe-link/pipe-link.d.ts +0 -1
- package/dist/index.cjs +0 -11793
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { defineExtension, } from "../../core/runtime";
|
|
3
|
+
import { CursorSourceBuilder } from "../../core/mapping/cursor-source-map";
|
|
4
|
+
import { CakeLinkPopover } from "./link-popover";
|
|
5
|
+
import { getDocLines } from "../../engine/selection/selection-layout";
|
|
6
|
+
import { cursorOffsetToVisibleOffset, getVisibleText, } from "../../engine/selection/visible-text";
|
|
7
|
+
import { ensureHttpsProtocol, isUrl } from "../../shared/url";
|
|
8
|
+
const LINK_KIND = "link";
|
|
9
|
+
export const linkExtension = defineExtension({
|
|
10
|
+
name: "link",
|
|
11
|
+
inlineWrapperAffinity: [{ kind: LINK_KIND, inclusive: false }],
|
|
12
|
+
keybindings: [
|
|
13
|
+
{
|
|
14
|
+
key: "u",
|
|
15
|
+
meta: true,
|
|
16
|
+
shift: true,
|
|
17
|
+
command: (state) => {
|
|
18
|
+
if (state.selection.start === state.selection.end) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
return { type: "wrap-link", openPopover: true };
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
key: "u",
|
|
26
|
+
ctrl: true,
|
|
27
|
+
shift: true,
|
|
28
|
+
command: (state) => {
|
|
29
|
+
if (state.selection.start === state.selection.end) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
return { type: "wrap-link", openPopover: true };
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
onEdit(command, state) {
|
|
37
|
+
if (command.type === "unlink") {
|
|
38
|
+
// Find the link at the given cursor position and remove the link markup
|
|
39
|
+
const cursorPos = command.start;
|
|
40
|
+
const sourcePos = state.map.cursorToSource(cursorPos, "forward");
|
|
41
|
+
const source = state.source;
|
|
42
|
+
// Search backwards for the opening bracket
|
|
43
|
+
let linkStart = sourcePos;
|
|
44
|
+
while (linkStart > 0 && source[linkStart] !== "[") {
|
|
45
|
+
linkStart--;
|
|
46
|
+
}
|
|
47
|
+
if (source[linkStart] !== "[") {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
// Find the ]( separator
|
|
51
|
+
const labelClose = source.indexOf("](", linkStart + 1);
|
|
52
|
+
if (labelClose === -1) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
// Find the closing )
|
|
56
|
+
const urlClose = source.indexOf(")", labelClose + 2);
|
|
57
|
+
if (urlClose === -1) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
// Extract the label (text between [ and ]( )
|
|
61
|
+
const label = source.slice(linkStart + 1, labelClose);
|
|
62
|
+
// Replace [label](url) with just label
|
|
63
|
+
const nextSource = source.slice(0, linkStart) + label + source.slice(urlClose + 1);
|
|
64
|
+
// Calculate new cursor position - place it at the end of the label
|
|
65
|
+
const newState = state.runtime.createState(nextSource);
|
|
66
|
+
const labelEndSource = linkStart + label.length;
|
|
67
|
+
const newCursor = newState.map.sourceToCursor(labelEndSource, "forward");
|
|
68
|
+
return {
|
|
69
|
+
source: nextSource,
|
|
70
|
+
selection: {
|
|
71
|
+
start: newCursor.cursorOffset,
|
|
72
|
+
end: newCursor.cursorOffset,
|
|
73
|
+
affinity: "forward",
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
if (command.type !== "wrap-link") {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
const selection = state.selection;
|
|
81
|
+
const cursorStart = Math.min(selection.start, selection.end);
|
|
82
|
+
const cursorEnd = Math.max(selection.start, selection.end);
|
|
83
|
+
if (cursorStart === cursorEnd) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
const from = state.map.cursorToSource(cursorStart, "forward");
|
|
87
|
+
const to = state.map.cursorToSource(cursorEnd, "backward");
|
|
88
|
+
if (from === to) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
const label = state.source.slice(from, to);
|
|
92
|
+
const url = command.url ?? "";
|
|
93
|
+
const linkMarkdown = `[${label}](${url})`;
|
|
94
|
+
const nextSource = state.source.slice(0, from) + linkMarkdown + state.source.slice(to);
|
|
95
|
+
return {
|
|
96
|
+
source: nextSource,
|
|
97
|
+
selection: {
|
|
98
|
+
start: cursorEnd,
|
|
99
|
+
end: cursorEnd,
|
|
100
|
+
affinity: "backward",
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
},
|
|
104
|
+
onPasteText(text, state) {
|
|
105
|
+
if (!isUrl(text)) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
const url = ensureHttpsProtocol(text.trim());
|
|
109
|
+
const selection = state.selection;
|
|
110
|
+
const start = Math.min(selection.start, selection.end);
|
|
111
|
+
const end = Math.max(selection.start, selection.end);
|
|
112
|
+
if (start !== end) {
|
|
113
|
+
const lines = getDocLines(state.doc);
|
|
114
|
+
const visibleText = getVisibleText(lines);
|
|
115
|
+
const visibleStart = cursorOffsetToVisibleOffset(lines, start);
|
|
116
|
+
const visibleEnd = cursorOffsetToVisibleOffset(lines, end);
|
|
117
|
+
const selectedText = visibleText.slice(visibleStart, visibleEnd);
|
|
118
|
+
const linkMarkdown = `[${selectedText}](${url})`;
|
|
119
|
+
return { type: "insert", text: linkMarkdown };
|
|
120
|
+
}
|
|
121
|
+
const linkMarkdown = `[${url}](${url})`;
|
|
122
|
+
return { type: "insert", text: linkMarkdown };
|
|
123
|
+
},
|
|
124
|
+
parseInline(source, start, end, context) {
|
|
125
|
+
if (source[start] !== "[") {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
// Don't match image syntax 
|
|
129
|
+
if (start > 0 && source[start - 1] === "!") {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
const labelClose = source.indexOf("](", start + 1);
|
|
133
|
+
if (labelClose === -1 || labelClose >= end) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
const urlClose = source.indexOf(")", labelClose + 2);
|
|
137
|
+
if (urlClose === -1 || urlClose >= end) {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
const labelStart = start + 1;
|
|
141
|
+
const labelEnd = labelClose;
|
|
142
|
+
const urlStart = labelClose + 2;
|
|
143
|
+
const urlEnd = urlClose;
|
|
144
|
+
const children = context.parseInline(source, labelStart, labelEnd);
|
|
145
|
+
const url = source.slice(urlStart, urlEnd);
|
|
146
|
+
return {
|
|
147
|
+
inline: {
|
|
148
|
+
type: "inline-wrapper",
|
|
149
|
+
kind: LINK_KIND,
|
|
150
|
+
children,
|
|
151
|
+
data: { url },
|
|
152
|
+
},
|
|
153
|
+
nextPos: urlClose + 1,
|
|
154
|
+
};
|
|
155
|
+
},
|
|
156
|
+
serializeInline(inline, context) {
|
|
157
|
+
if (inline.type !== "inline-wrapper" || inline.kind !== LINK_KIND) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
const builder = new CursorSourceBuilder();
|
|
161
|
+
builder.appendSourceOnly("[");
|
|
162
|
+
for (const child of inline.children) {
|
|
163
|
+
const serialized = context.serializeInline(child);
|
|
164
|
+
builder.appendSerialized(serialized);
|
|
165
|
+
}
|
|
166
|
+
builder.appendSourceOnly("](");
|
|
167
|
+
const url = typeof inline.data?.url === "string" ? inline.data.url : "";
|
|
168
|
+
builder.appendSourceOnly(url);
|
|
169
|
+
builder.appendSourceOnly(")");
|
|
170
|
+
return builder.build();
|
|
171
|
+
},
|
|
172
|
+
normalizeInline(inline) {
|
|
173
|
+
if (inline.type !== "inline-wrapper" || inline.kind !== LINK_KIND) {
|
|
174
|
+
return inline;
|
|
175
|
+
}
|
|
176
|
+
if (inline.children.length === 0) {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
return inline;
|
|
180
|
+
},
|
|
181
|
+
renderInline(inline, context) {
|
|
182
|
+
if (inline.type !== "inline-wrapper" || inline.kind !== LINK_KIND) {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
const element = document.createElement("a");
|
|
186
|
+
element.className = "cake-link";
|
|
187
|
+
const url = typeof inline.data?.url === "string" ? inline.data.url : "";
|
|
188
|
+
element.setAttribute("href", url);
|
|
189
|
+
for (const child of inline.children) {
|
|
190
|
+
for (const node of context.renderInline(child)) {
|
|
191
|
+
element.append(node);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return element;
|
|
195
|
+
},
|
|
196
|
+
renderOverlay(context) {
|
|
197
|
+
if (!context.contentRoot || !context.toOverlayRect) {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
return (_jsx(CakeLinkPopover, { container: context.container, contentRoot: context.contentRoot, toOverlayRect: context.toOverlayRect, getSelection: context.getSelection, executeCommand: context.executeCommand }));
|
|
201
|
+
},
|
|
202
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/cake/extensions/list/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./list";
|
|
@@ -48,3 +48,4 @@ export declare function isListLine(line: string): boolean;
|
|
|
48
48
|
export declare function parseListItem(line: string): ListItem | null;
|
|
49
49
|
export declare function countNumberedItemsBefore(source: string, beforeLineIndex: number): number;
|
|
50
50
|
export {};
|
|
51
|
+
//# sourceMappingURL=list-ast.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-ast.d.ts","sourceRoot":"","sources":["../../../../src/cake/extensions/list/list-ast.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,KAAK,UAAU,GAAG,QAAQ,GAAG,UAAU,CAAC;AAExC,UAAU,QAAQ;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,UAAU,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,UAAU,SAAS;IACjB,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,UAAU,YAAY;IACpB,IAAI,EAAE,WAAW,CAAC;IAClB,IAAI,EAAE,QAAQ,CAAC;CAChB;AAED,KAAK,QAAQ,GAAG,SAAS,GAAG,YAAY,CAAC;AAEzC,UAAU,SAAS;IACjB,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AA0BD,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,GAChB,SAAS,CAMX;AAED,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,SAAS,EAChB,WAAW,GAAE,MAAU,GACtB,MAAM,CAqCR;AAID,wBAAgB,eAAe,CAC7B,KAAK,EAAE,SAAS,EAChB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,QAAQ,GACb,SAAS,CAIX;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,CAIzE;AAED,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,SAAS,EAChB,SAAS,EAAE,MAAM,GAChB,SAAS,CASX;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,CAYzE;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,CAiB1E;AAED,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,SAAS,EAChB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GACd,SAAS,CAYX;AAED,wBAAgB,UAAU,CACxB,KAAK,EAAE,SAAS,EAChB,eAAe,EAAE,MAAM,EACvB,eAAe,EAAE,MAAM,GACtB,SAAS,CAeX;AAGD,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,GACnB;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CA4DtE;AAGD,wBAAgB,WAAW,CACzB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,GACb;IACD,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;CACtB,CA4BA;AAGD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM/D;AAGD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEhD;AAGD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAM3D;AAGD,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,EACd,eAAe,EAAE,MAAM,GACtB,MAAM,CAUR"}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List AST - a structured representation of list content that can be
|
|
3
|
+
* transformed and serialized back to text with correct numbering.
|
|
4
|
+
*/
|
|
5
|
+
// Match list lines - capture exactly one space after marker, rest goes to content
|
|
6
|
+
const LIST_LINE_REGEX = /^(\s*)([-*+]|\d+\.)( )(.*)$/;
|
|
7
|
+
const INDENT_SIZE = 2;
|
|
8
|
+
function parseMarkerType(marker) {
|
|
9
|
+
return /^\d+\.$/.test(marker) ? "numbered" : "bullet";
|
|
10
|
+
}
|
|
11
|
+
function parseLine(line) {
|
|
12
|
+
const match = line.match(LIST_LINE_REGEX);
|
|
13
|
+
if (!match) {
|
|
14
|
+
return { type: "plain", content: line };
|
|
15
|
+
}
|
|
16
|
+
const indent = Math.floor(match[1].length / INDENT_SIZE);
|
|
17
|
+
const markerType = parseMarkerType(match[2]);
|
|
18
|
+
const content = match[4] ?? "";
|
|
19
|
+
return {
|
|
20
|
+
type: "list-item",
|
|
21
|
+
item: { indent, markerType, content },
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export function parseListRange(source, startOffset, endOffset) {
|
|
25
|
+
const text = source.slice(startOffset, endOffset);
|
|
26
|
+
const rawLines = text.split("\n");
|
|
27
|
+
const lines = rawLines.map(parseLine);
|
|
28
|
+
return { lines, startOffset, endOffset };
|
|
29
|
+
}
|
|
30
|
+
export function serializeListRange(range, startNumber = 1) {
|
|
31
|
+
// Track current number at each indent level
|
|
32
|
+
const numbersByIndent = new Map();
|
|
33
|
+
numbersByIndent.set(0, startNumber);
|
|
34
|
+
return range.lines
|
|
35
|
+
.map((line) => {
|
|
36
|
+
if (line.type === "plain") {
|
|
37
|
+
// Only blank lines reset numbering
|
|
38
|
+
if (line.content.trim() === "") {
|
|
39
|
+
numbersByIndent.clear();
|
|
40
|
+
}
|
|
41
|
+
return line.content;
|
|
42
|
+
}
|
|
43
|
+
const { item } = line;
|
|
44
|
+
const indentStr = " ".repeat(item.indent * INDENT_SIZE);
|
|
45
|
+
if (item.markerType === "bullet") {
|
|
46
|
+
// Bullet doesn't affect numbering
|
|
47
|
+
return `${indentStr}- ${item.content}`;
|
|
48
|
+
}
|
|
49
|
+
// Numbered: get current number at this indent level
|
|
50
|
+
const currentNum = numbersByIndent.get(item.indent) ?? 1;
|
|
51
|
+
numbersByIndent.set(item.indent, currentNum + 1);
|
|
52
|
+
// Reset numbering for deeper indent levels
|
|
53
|
+
for (const [indent] of numbersByIndent) {
|
|
54
|
+
if (indent > item.indent) {
|
|
55
|
+
numbersByIndent.delete(indent);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return `${indentStr}${currentNum}. ${item.content}`;
|
|
59
|
+
})
|
|
60
|
+
.join("\n");
|
|
61
|
+
}
|
|
62
|
+
// AST Operations
|
|
63
|
+
export function insertItemAfter(range, lineIndex, item) {
|
|
64
|
+
const newLines = [...range.lines];
|
|
65
|
+
newLines.splice(lineIndex + 1, 0, { type: "list-item", item });
|
|
66
|
+
return { ...range, lines: newLines };
|
|
67
|
+
}
|
|
68
|
+
export function removeItem(range, lineIndex) {
|
|
69
|
+
const newLines = [...range.lines];
|
|
70
|
+
newLines.splice(lineIndex, 1);
|
|
71
|
+
return { ...range, lines: newLines };
|
|
72
|
+
}
|
|
73
|
+
export function convertToPlainText(range, lineIndex) {
|
|
74
|
+
const line = range.lines[lineIndex];
|
|
75
|
+
if (line.type !== "list-item") {
|
|
76
|
+
return range;
|
|
77
|
+
}
|
|
78
|
+
const newLines = [...range.lines];
|
|
79
|
+
newLines[lineIndex] = { type: "plain", content: line.item.content };
|
|
80
|
+
return { ...range, lines: newLines };
|
|
81
|
+
}
|
|
82
|
+
export function indentItem(range, lineIndex) {
|
|
83
|
+
const line = range.lines[lineIndex];
|
|
84
|
+
if (line.type !== "list-item") {
|
|
85
|
+
return range;
|
|
86
|
+
}
|
|
87
|
+
const newLines = [...range.lines];
|
|
88
|
+
newLines[lineIndex] = {
|
|
89
|
+
type: "list-item",
|
|
90
|
+
item: { ...line.item, indent: line.item.indent + 1 },
|
|
91
|
+
};
|
|
92
|
+
return { ...range, lines: newLines };
|
|
93
|
+
}
|
|
94
|
+
export function outdentItem(range, lineIndex) {
|
|
95
|
+
const line = range.lines[lineIndex];
|
|
96
|
+
if (line.type !== "list-item") {
|
|
97
|
+
return range;
|
|
98
|
+
}
|
|
99
|
+
if (line.item.indent === 0) {
|
|
100
|
+
// Top-level item: convert to plain text
|
|
101
|
+
return convertToPlainText(range, lineIndex);
|
|
102
|
+
}
|
|
103
|
+
const newLines = [...range.lines];
|
|
104
|
+
newLines[lineIndex] = {
|
|
105
|
+
type: "list-item",
|
|
106
|
+
item: { ...line.item, indent: line.item.indent - 1 },
|
|
107
|
+
};
|
|
108
|
+
return { ...range, lines: newLines };
|
|
109
|
+
}
|
|
110
|
+
export function updateItemContent(range, lineIndex, content) {
|
|
111
|
+
const line = range.lines[lineIndex];
|
|
112
|
+
if (line.type !== "list-item") {
|
|
113
|
+
return range;
|
|
114
|
+
}
|
|
115
|
+
const newLines = [...range.lines];
|
|
116
|
+
newLines[lineIndex] = {
|
|
117
|
+
type: "list-item",
|
|
118
|
+
item: { ...line.item, content },
|
|
119
|
+
};
|
|
120
|
+
return { ...range, lines: newLines };
|
|
121
|
+
}
|
|
122
|
+
export function mergeItems(range, targetLineIndex, sourceLineIndex) {
|
|
123
|
+
const targetLine = range.lines[targetLineIndex];
|
|
124
|
+
const sourceLine = range.lines[sourceLineIndex];
|
|
125
|
+
if (targetLine.type !== "list-item" || sourceLine.type !== "list-item") {
|
|
126
|
+
return range;
|
|
127
|
+
}
|
|
128
|
+
const mergedContent = targetLine.item.content +
|
|
129
|
+
(sourceLine.item.content ? " " + sourceLine.item.content : "");
|
|
130
|
+
let result = updateItemContent(range, targetLineIndex, mergedContent);
|
|
131
|
+
result = removeItem(result, sourceLineIndex);
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
134
|
+
// Helper to find list range boundaries from a cursor position
|
|
135
|
+
export function findListRangeBoundaries(source, cursorOffset) {
|
|
136
|
+
const lines = source.split("\n");
|
|
137
|
+
let offset = 0;
|
|
138
|
+
let cursorLineIndex = 0;
|
|
139
|
+
// Find which line the cursor is on
|
|
140
|
+
for (let i = 0; i < lines.length; i++) {
|
|
141
|
+
const lineEnd = offset + lines[i].length;
|
|
142
|
+
if (cursorOffset >= offset && cursorOffset <= lineEnd) {
|
|
143
|
+
cursorLineIndex = i;
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
offset = lineEnd + 1;
|
|
147
|
+
}
|
|
148
|
+
// Check if current line is a list item
|
|
149
|
+
const currentLine = lines[cursorLineIndex];
|
|
150
|
+
if (!LIST_LINE_REGEX.test(currentLine)) {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
// Expand backwards to find start of list
|
|
154
|
+
let startLineIndex = cursorLineIndex;
|
|
155
|
+
while (startLineIndex > 0) {
|
|
156
|
+
const prevLine = lines[startLineIndex - 1];
|
|
157
|
+
if (prevLine.trim() === "" || !LIST_LINE_REGEX.test(prevLine)) {
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
startLineIndex--;
|
|
161
|
+
}
|
|
162
|
+
// Expand forwards to find end of list
|
|
163
|
+
let endLineIndex = cursorLineIndex;
|
|
164
|
+
while (endLineIndex < lines.length - 1) {
|
|
165
|
+
const nextLine = lines[endLineIndex + 1];
|
|
166
|
+
if (nextLine.trim() === "" || !LIST_LINE_REGEX.test(nextLine)) {
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
endLineIndex++;
|
|
170
|
+
}
|
|
171
|
+
// Calculate offsets
|
|
172
|
+
let startOffset = 0;
|
|
173
|
+
for (let i = 0; i < startLineIndex; i++) {
|
|
174
|
+
startOffset += lines[i].length + 1;
|
|
175
|
+
}
|
|
176
|
+
let endOffset = startOffset;
|
|
177
|
+
for (let i = startLineIndex; i <= endLineIndex; i++) {
|
|
178
|
+
endOffset += lines[i].length;
|
|
179
|
+
if (i < endLineIndex) {
|
|
180
|
+
endOffset += 1;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
startOffset,
|
|
185
|
+
endOffset,
|
|
186
|
+
lineIndex: cursorLineIndex - startLineIndex,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
// Get line info at a specific offset
|
|
190
|
+
export function getLineInfo(source, offset) {
|
|
191
|
+
const lines = source.split("\n");
|
|
192
|
+
let pos = 0;
|
|
193
|
+
for (let i = 0; i < lines.length; i++) {
|
|
194
|
+
const lineStart = pos;
|
|
195
|
+
const lineEnd = pos + lines[i].length;
|
|
196
|
+
if (offset >= lineStart && offset <= lineEnd) {
|
|
197
|
+
return {
|
|
198
|
+
lineIndex: i,
|
|
199
|
+
lineStart,
|
|
200
|
+
lineEnd,
|
|
201
|
+
line: lines[i],
|
|
202
|
+
offsetInLine: offset - lineStart,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
pos = lineEnd + 1;
|
|
206
|
+
}
|
|
207
|
+
const lastLine = lines[lines.length - 1] ?? "";
|
|
208
|
+
const lastLineStart = source.length - lastLine.length;
|
|
209
|
+
return {
|
|
210
|
+
lineIndex: lines.length - 1,
|
|
211
|
+
lineStart: lastLineStart,
|
|
212
|
+
lineEnd: source.length,
|
|
213
|
+
line: lastLine,
|
|
214
|
+
offsetInLine: offset - lastLineStart,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
// Get the prefix length for a list line (indent + marker + space)
|
|
218
|
+
export function getListPrefixLength(line) {
|
|
219
|
+
const match = line.match(LIST_LINE_REGEX);
|
|
220
|
+
if (!match) {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
return match[1].length + match[2].length + match[3].length;
|
|
224
|
+
}
|
|
225
|
+
// Check if a line is a list item
|
|
226
|
+
export function isListLine(line) {
|
|
227
|
+
return LIST_LINE_REGEX.test(line);
|
|
228
|
+
}
|
|
229
|
+
// Parse a single line and return the list item if it is one
|
|
230
|
+
export function parseListItem(line) {
|
|
231
|
+
const parsed = parseLine(line);
|
|
232
|
+
if (parsed.type !== "list-item") {
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
return parsed.item;
|
|
236
|
+
}
|
|
237
|
+
// Count numbered items at top level (indent 0) before a given line index
|
|
238
|
+
export function countNumberedItemsBefore(source, beforeLineIndex) {
|
|
239
|
+
const lines = source.split("\n");
|
|
240
|
+
let count = 0;
|
|
241
|
+
for (let i = 0; i < beforeLineIndex && i < lines.length; i++) {
|
|
242
|
+
const item = parseListItem(lines[i]);
|
|
243
|
+
if (item && item.markerType === "numbered" && item.indent === 0) {
|
|
244
|
+
count++;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return count;
|
|
248
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../../../src/cake/extensions/list/list.ts"],"names":[],"mappings":"AAygCA,+CAA+C;AAC/C,MAAM,MAAM,uBAAuB,GAAG;IAAE,IAAI,EAAE,oBAAoB,CAAA;CAAE,CAAC;AAErE,iDAAiD;AACjD,MAAM,MAAM,yBAAyB,GAAG;IAAE,IAAI,EAAE,sBAAsB,CAAA;CAAE,CAAC;AAEzE,kCAAkC;AAClC,MAAM,MAAM,WAAW,GAAG,uBAAuB,GAAG,yBAAyB,CAAC;AAE9E,eAAO,MAAM,aAAa,kCAmGxB,CAAC"}
|