Fix bulleted list toolbar button active state (#651)
* Fix bulleted list toolbar button active state (#642) Add para.state command to return paragraph formatting state (bullets, numbering, multilevel) and update ribbon to use it for list button active states. Previously, the bullets, numbering, and multilevel list buttons never showed as active even when the cursor was inside a list item. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * Review fixes for #651: list buttons from the paragraph's own level - The Bullets/Numbering state reads the paragraph's list level (ilvl), not level 0, and Multilevel is on when the list numbers its levels together (a label showing another level's number, or legal numbering); the old level-0 '%2.' check was never true. - Computed from the paragraph props the ribbon already resolves (cmd::para::list_buttons) instead of a new para.state command. - Engine test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * #651: list-button test starts legal numbering from plain text Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
2f58d441b6
commit
6cf0aec0ff
|
|
@ -1146,3 +1146,29 @@ fn own_format(st: &Style) -> Value {
|
|||
pub fn touch_all(s: &mut Session) {
|
||||
s.dirty = true;
|
||||
}
|
||||
|
||||
/// Which Home › Paragraph list buttons show as on.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub struct ListButtons {
|
||||
pub bullets: bool,
|
||||
pub numbering: bool,
|
||||
pub multilevel: bool,
|
||||
}
|
||||
|
||||
/// The list buttons for a paragraph with resolved `numbering` (the ribbon passes the paragraph
|
||||
/// props it already resolved). The paragraph's own level decides Bullets (a bulleted level) or
|
||||
/// Numbering (a numbered one); Multilevel is on too when the list numbers its levels together:
|
||||
/// some level's label shows another level's number (`1.1.`) or uses legal numbering.
|
||||
pub fn list_buttons(doc: &wordcraft_doc::Document, numbering: Option<NumRef>) -> ListButtons {
|
||||
let Some(n) = numbering.filter(|n| n.num != 0) else { return ListButtons::default() };
|
||||
let Some(level) = doc.numbering.level(n.num, n.level) else { return ListButtons::default() };
|
||||
let bullets = level.format == wordcraft_doc::section::NumFormat::Bullet;
|
||||
let joined = |i: u8| {
|
||||
doc.numbering.level(n.num, i).is_some_and(|l| {
|
||||
let own = u32::from(i) + 1;
|
||||
l.legal || l.text.split('%').skip(1).filter_map(|t| t.chars().next()?.to_digit(10)).any(|k| k != own)
|
||||
})
|
||||
};
|
||||
let multilevel = (0..9u8).any(joined);
|
||||
ListButtons { bullets, numbering: !bullets, multilevel }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3597,6 +3597,38 @@ fn enclose_characters_make_an_eq_field_and_come_back_out() {
|
|||
assert_eq!(field(&s), Some(("eq \\o\\ac(△,A)".to_string(), "A".to_string())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_buttons_follow_the_paragraphs_own_level() {
|
||||
use crate::cmd::para::{ListButtons, list_buttons};
|
||||
let mut s = s();
|
||||
run(&mut s, "text.insert", json!({"text": "item"}));
|
||||
let state = |s: &Session| {
|
||||
let rp = s.doc.para_at(&s.sel.focus).map(|p| s.doc.styles.resolve_para(&p.props));
|
||||
list_buttons(&s.doc, rp.and_then(|r| r.numbering))
|
||||
};
|
||||
assert_eq!(state(&s), ListButtons::default(), "plain text: no list button on");
|
||||
run(&mut s, "para.bullets", json!({}));
|
||||
assert_eq!(state(&s), ListButtons { bullets: true, numbering: false, multilevel: false });
|
||||
// A bulleted list whose second level is numbered: at level 2 the paragraph is numbered.
|
||||
let num = s.doc.para_at(&s.sel.focus).unwrap().props.numbering.unwrap().num;
|
||||
let numbered = wordcraft_doc::numbering::Level { text: "%2.".into(), ..Default::default() };
|
||||
s.doc.numbering.nums.iter_mut().find(|n| n.id == num).unwrap().level_overrides.push((1, numbered));
|
||||
run(&mut s, "para.listLevel", json!({"level": 1}));
|
||||
assert_eq!(state(&s), ListButtons { bullets: false, numbering: true, multilevel: false });
|
||||
run(&mut s, "para.numbering", json!({}));
|
||||
assert_eq!(state(&s), ListButtons { bullets: false, numbering: true, multilevel: false }, "1. a. i. is not multilevel");
|
||||
// Legal numbering (1. 1.1. 1.1.1.) numbers levels together, even on its first level. (Its
|
||||
// first level reads like "1." numbering, which the button would toggle off, so start plain.)
|
||||
run(&mut s, "para.numbering", json!({"off": true}));
|
||||
run(&mut s, "para.multilevel", json!({"kind": "legal"}));
|
||||
run(&mut s, "para.listLevel", json!({"level": 0}));
|
||||
assert_eq!(state(&s), ListButtons { bullets: false, numbering: true, multilevel: true });
|
||||
run(&mut s, "para.listLevel", json!({"level": 2}));
|
||||
assert!(state(&s).multilevel);
|
||||
run(&mut s, "para.numbering", json!({"off": true}));
|
||||
assert_eq!(state(&s), ListButtons::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replace_all_splits_paragraphs_at_a_paragraph_mark_in_the_replacement() {
|
||||
// Issue #423: untracked Replace All left `\r` in the paragraph instead of splitting it.
|
||||
|
|
|
|||
|
|
@ -560,9 +560,10 @@ fn home(app: &mut WordApp, ui: &mut Ui) {
|
|||
// The buttons show alignment as seen on the page (Align Right is a right-to-left paragraph's start).
|
||||
let align = rp.as_ref().map(|r| r.align.visual(r.bidi)).unwrap_or_default();
|
||||
let rtl = rp.as_ref().is_some_and(|r| r.bidi);
|
||||
let lists = wordcraft_engine::cmd::para::list_buttons(&app.session.doc, rp.as_ref().and_then(|r| r.numbering));
|
||||
stack(ui, |ui| {
|
||||
crate::widgets::row(ui, |ui| {
|
||||
split(ui, app, "bullets", "Bullets", "para.bullets", json!({}), false, None, |ui, app| {
|
||||
split(ui, app, "bullets", "Bullets", "para.bullets", json!({}), lists.bullets, None, |ui, app| {
|
||||
ui.label(egui::RichText::new(tl!("Bullet Library")).small().weak());
|
||||
ui.horizontal(|ui| {
|
||||
for c in ["•", "○", "▪", "◆", "➢", "✓", "–"] {
|
||||
|
|
@ -583,7 +584,7 @@ fn home(app: &mut WordApp, ui: &mut Ui) {
|
|||
});
|
||||
mi(ui, app, "None", "para.bullets", json!({"off": true}));
|
||||
});
|
||||
split(ui, app, "numbering", "Numbering", "para.numbering", json!({}), false, None, |ui, app| {
|
||||
split(ui, app, "numbering", "Numbering", "para.numbering", json!({}), lists.numbering, None, |ui, app| {
|
||||
for (l, k) in [
|
||||
("1. 2. 3.", "numbered"),
|
||||
("1) 2) 3)", "numberedParen"),
|
||||
|
|
@ -598,7 +599,7 @@ fn home(app: &mut WordApp, ui: &mut Ui) {
|
|||
mi(ui, app, "Restart at 1", "para.restartNumbering", json!({}));
|
||||
mi(ui, app, "None", "para.numbering", json!({"off": true}));
|
||||
});
|
||||
split(ui, app, "multilevel", "Multilevel List", "para.multilevel", json!({}), false, None, |ui, app| {
|
||||
split(ui, app, "multilevel", "Multilevel List", "para.multilevel", json!({}), lists.multilevel, None, |ui, app| {
|
||||
mi(ui, app, "1. 1.1. 1.1.1.", "para.multilevel", json!({"kind": "legal"}));
|
||||
mi(ui, app, "I. A. 1. a.", "para.multilevel", json!({"kind": "outline"}));
|
||||
ui.separator();
|
||||
|
|
|
|||
Loading…
Reference in New Issue