mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-28 03:37:00 -06:00
Type: typing "1. ", "A) " or "* " at the start of a paragraph starts a list
The reference app detects lists typed as 1., 1), A. or A), and makes custom bullets from *, #, > or + followed by a space. In VectorCraft the characters stayed as text. When the Type tool types a space on its own after one of these at the start of a paragraph in no list, the typing so far becomes one undo step. The new command text.startList then removes the typed characters and the space and makes the paragraph a numbered list in 1, 2, 3 or A, B, C, or a bulleted list with the filled dot. A paragraph with no indents gets the 18 pt hanging indent. One Undo puts the characters back as text with no list, and the caret goes on after them; Redo removes them again. The same characters later in a paragraph, list characters pasted in one piece with the space and a paragraph already in a list start nothing. Docs: docs/mcp.md, docs/target-app-parity.md.
This commit is contained in:
@@ -22,6 +22,15 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
has_doc,
|
||||
edit_range
|
||||
),
|
||||
cmd!(
|
||||
"text.startList",
|
||||
"Bullets and Numbering",
|
||||
[],
|
||||
None,
|
||||
"{id, at?: byte (default: the end of the text)} start a list from typed characters, what the Type tool runs when a space is typed on its own: when the text from the start of the paragraph that `at` is in up to `at` is \"1. \" or \"1) \" (a numbered list in 1, 2, 3), \"A. \" or \"A) \" (a numbered list in A, B, C), or \"* \", \"# \", \"> \" or \"+ \" (a bulleted list with the filled dot), and the paragraph is in no list, those characters are removed and the paragraph joins that list as one undo step (left and first-line indents of 0 become 18 and -18, as text.setFormat sets them); otherwise nothing changes → {id, started: bool, caret: byte (the paragraph's start when started, else `at`)}",
|
||||
has_doc,
|
||||
start_list
|
||||
),
|
||||
cmd!(
|
||||
"text.setRangeStyle",
|
||||
"Character",
|
||||
@@ -220,6 +229,36 @@ fn edit_range(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
Ok(json!({"id": id.0, "caret": caret}))
|
||||
}
|
||||
|
||||
fn start_list(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
const C: &str = "text.startList";
|
||||
let id = id_param(p, "id").ok_or_else(|| bad(C, "missing `id`"))?;
|
||||
check_numbers(p, &["at"], C)?;
|
||||
let t = text_ref(s, id)?;
|
||||
let len = edit::runs_len(&t.runs);
|
||||
let at = byte_param(p, "at").unwrap_or(len).min(len);
|
||||
let Some((start, list)) = t.typed_list_at(at) else {
|
||||
return Ok(json!({"id": id.0, "started": false, "caret": at}));
|
||||
};
|
||||
s.edit("Bullets and Numbering", |d, _| {
|
||||
let t = text_mut(d, id).ok_or(EngineError::NoNode(id))?;
|
||||
t.splice_paras(start, at, "");
|
||||
edit::replace_range(&mut t.runs, start, at, "");
|
||||
t.ruby_after_replace(start, at, 0);
|
||||
let i = t.paragraphs_in(start, start).start;
|
||||
t.edit_paras(Some(i..i + 1), |para| {
|
||||
para.list = Some(list);
|
||||
// A paragraph with no left or first-line indent gets the hanging indent.
|
||||
if para.left_indent == 0.0 && para.first_line_indent == 0.0 {
|
||||
para.left_indent = vectorcraft_doc::text::LIST_HANGING_INDENT;
|
||||
para.first_line_indent = -vectorcraft_doc::text::LIST_HANGING_INDENT;
|
||||
}
|
||||
});
|
||||
refresh_bounds(t);
|
||||
Ok(())
|
||||
})?;
|
||||
Ok(json!({"id": id.0, "started": true, "caret": start}))
|
||||
}
|
||||
|
||||
fn get_range(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let id = id_param(p, "id").ok_or_else(|| bad("text.getRange", "missing `id`"))?;
|
||||
let t = text_ref(s, id)?;
|
||||
|
||||
Reference in New Issue
Block a user