fix(web): numbered composer lists nest and lift correctly#17138
0 / 5 viewed5 files changedLine totals unavailable
scripts/fixtures/issue-17124/seed.js · Earlier revision ae038086 · right 55
Original diff
@@ -0,0 +1,107 @@
+// Run in the isolated dev app's preview_evaluate context. Never sends a turn.
+export const fixtures = {
+ A: ["1. parent\n2. ch|ild", "10. parent\n11. ch|ild"],
+ B: ["2. parent\n 1. sibling\n 1. grandchild\n3. ch|ild"],
+ C: ["1. parent\n 1. child\n 1. |"],
+ D: ["7. solo|", "1. parent\n\n2. ch|ild", "10. parent\n|11. child"],
+ E: [
+ "- parent\n- ch|ild",
+ "- [x] parent\n- [ ] ch|ild",
+ "1. parent\n 09) sibling\n2) ch|ild",
+ "1. parent\n 09) sibling\n2. ch|ild",
+ ],
+};
+
+export async function seed(marked) {
+ const { buildDocJson, serializeEditorDoc } = await import("/src/composer-rich-text-doc.ts");
+ const element = document.querySelector('[data-testid="composer-editor"]');
+ if (!element?.editor) throw new Error("Open an empty synthetic ChatComposer first");
+ const editor = element.editor;
+ const rich = element.dataset.composerRichText === "true";
+ const offset = marked.indexOf("|");
+ const text = marked.replace("|", "");
+ const doc = editor.schema.nodeFromJSON(buildDocJson(text, () => null, { styling: rich }));
+ editor.view.dispatch(editor.state.tr.replaceWith(0, editor.state.doc.content.size, doc.content));
+ const map = serializeEditorDoc(editor.state.doc);
+ // Structural rich markers have no editable prefix position. Use content start.
+ const run =
+ map.runs.find(
+ (r) => r.kind === "text" && offset >= r.mdStart && offset <= r.mdStart + r.mdLen,
+ ) ??
+ map.runs.find((r) => r.mdStart >= offset && r.kind !== "prefix") ??
+ map.runs.at(-1);
+ const pos = Math.min(
+ editor.state.doc.content.size - 1,
+ run ? run.pmPos + Math.max(0, Math.min(run.docLen, offset - run.mdStart)) : 1,
+ );
+ editor.view.dispatch(
+ editor.state.tr.setSelection(editor.state.selection.constructor.create(editor.state.doc, pos)),
+ );
+ editor.view.focus();
+ return { requested: text, canonical: map.value, requestedOffset: offset, pmPos: pos, rich };
+}
+
+export async function observe() {
+ const { serializeEditorDoc } = await import("/src/composer-rich-text-doc.ts");
+ const editor = document.querySelector('[data-testid="composer-editor"]').editor;
+ return {
+ text: serializeEditorDoc(editor.state.doc).value,
+ selection: editor.state.selection.toJSON(),
+ };
+}
+
+export async function installRpc(environmentId) {
+ const { createEnvironmentRpcCommand } =
+ await import("/@fs/home/ubuntu/wt/issue-17124/packages/client-runtime/src/state/runtime.ts");apps/web/src/composer-list-continuation.ts · Earlier revision ae038086 · right 167
Original diff
@@ -149,6 +149,27 @@ function currentLine(value: string, cursor: number): { start: number; end: numbe
return { start, end, text: value.slice(start, end) };
}
+function* listMarkersAbove(
+ value: string,
+ start: number,
+ tokens: ReturnType<typeof inlineTokenRanges>,
+) {
+ let tokenIndex = tokens.length - 1;
+ while (start > 0) {
+ let lineStart = value.lastIndexOf("\n", start - 2) + 1;
+ // A newline inside a chip does not start a list item.
+ for (let token = tokens[tokenIndex]; token; token = tokens[tokenIndex]) {
+ if (token.start >= lineStart) tokenIndex -= 1;
+ else if (token.end > lineStart) lineStart = value.lastIndexOf("\n", token.start - 1) + 1;
+ else break;
+ }
+ const parsed = parseListMarker(value.slice(lineStart, start - 1));
+ if (!parsed) return;