tangled
alpha
login
or
join now
nonbinary.computer
/
weaver
atproto blogging
24
fork
atom
overview
issues
2
pulls
pipelines
reworked a bit of list behaviour post input refactor
Orual
2 months ago
856e420f
db383970
+59
-21
2 changed files
expand all
collapse all
unified
split
crates
weaver-app
src
components
editor
actions.rs
weaver-renderer
src
css.rs
+58
-20
crates/weaver-app/src/components/editor/actions.rs
···
704
704
},
705
705
);
706
706
707
707
-
// === Dedicated editing keys (for custom keyboards, etc.) ===
708
707
bindings.insert(KeyCombo::new(Key::Undo), EditorAction::Undo);
709
708
bindings.insert(KeyCombo::new(Key::Redo), EditorAction::Redo);
710
709
bindings.insert(KeyCombo::new(Key::Copy), EditorAction::Copy);
···
722
721
723
722
/// Look up an action for the given key combo, with the current range applied.
724
723
pub fn lookup(&self, combo: KeyCombo, range: Range) -> Option<EditorAction> {
725
725
-
self.bindings.get(&combo).cloned().map(|a| a.with_range(range))
724
724
+
self.bindings
725
725
+
.get(&combo)
726
726
+
.cloned()
727
727
+
.map(|a| a.with_range(range))
726
728
}
727
729
728
730
/// Look up an action for the given key and modifiers, with the current range applied.
731
731
+
#[allow(dead_code)]
729
732
pub fn lookup_key(&self, key: Key, modifiers: Modifiers, range: Range) -> Option<EditorAction> {
730
733
self.lookup(KeyCombo::with_modifiers(key, modifiers), range)
731
734
}
732
735
733
736
/// Add or replace a keybinding.
737
737
+
#[allow(dead_code)]
734
738
pub fn bind(&mut self, combo: KeyCombo, action: EditorAction) {
735
739
self.bindings.insert(combo, action);
736
740
}
737
741
738
742
/// Remove a keybinding.
743
743
+
#[allow(dead_code)]
739
744
pub fn unbind(&mut self, combo: KeyCombo) {
740
745
self.bindings.remove(&combo);
741
746
}
···
792
797
let range = range.normalize();
793
798
doc.pending_snap.set(Some(SnapDirection::Forward));
794
799
800
800
+
let offset = range.start;
795
801
if !range.is_caret() {
796
796
-
let _ = doc.remove_tracked(range.start, range.len());
802
802
+
let _ = doc.remove_tracked(offset, range.len());
797
803
}
798
804
799
799
-
let mut offset = range.start;
800
800
-
801
805
// Check if we're right after a soft break (newline + zero-width char).
802
806
// If so, convert to paragraph break by replacing the zero-width char
803
807
// with a newline.
804
804
-
let mut is_double_enter = false;
805
805
-
if offset >= 2 {
808
808
+
let is_double_enter = if offset >= 2 {
806
809
let prev_char = get_char_at(doc.loro_text(), offset - 1);
807
810
let prev_prev_char = get_char_at(doc.loro_text(), offset - 2);
808
811
if prev_char == Some('\u{200C}') && prev_prev_char == Some('\n') {
809
809
-
// Replace zero-width char with newline
810
810
-
let _ = doc.replace_tracked(offset - 1, 1, "\n");
811
811
-
doc.cursor.write().offset = offset;
812
812
-
is_double_enter = true;
812
812
+
true
813
813
+
} else {
814
814
+
false
813
815
}
814
814
-
}
816
816
+
} else {
817
817
+
false
818
818
+
};
815
819
816
820
if !is_double_enter {
817
817
-
// Normal soft break: insert newline + zero-width char for cursor positioning.
818
818
-
// The renderer emits <br> for soft breaks, so we don't need
819
819
-
// trailing spaces for visual line breaks.
820
820
-
let _ = doc.insert_tracked(offset, "\n\u{200C}");
821
821
-
doc.cursor.write().offset = offset + 2;
821
821
+
// Check for list context
822
822
+
if let Some(ctx) = detect_list_context(doc.loro_text(), offset) {
823
823
+
tracing::debug!("List context detected: {:?}", ctx);
824
824
+
if is_list_item_empty(doc.loro_text(), offset, &ctx) {
825
825
+
// Empty item - exit list
826
826
+
let line_start = find_line_start(doc.loro_text(), offset);
827
827
+
let line_end = find_line_end(doc.loro_text(), offset);
828
828
+
let delete_end = (line_end + 1).min(doc.len_chars());
829
829
+
830
830
+
let _ = doc.replace_tracked(
831
831
+
line_start,
832
832
+
delete_end.saturating_sub(line_start),
833
833
+
"\n\n\u{200C}\n",
834
834
+
);
835
835
+
doc.cursor.write().offset = line_start + 2;
836
836
+
tracing::debug!("empty list");
837
837
+
} else {
838
838
+
// Continue list
839
839
+
let continuation = match ctx {
840
840
+
super::input::ListContext::Unordered { indent, marker } => {
841
841
+
format!("\n{}{} ", indent, marker)
842
842
+
}
843
843
+
super::input::ListContext::Ordered { indent, number } => {
844
844
+
format!("\n{}{}. ", indent, number + 1)
845
845
+
}
846
846
+
};
847
847
+
let len = continuation.chars().count();
848
848
+
let _ = doc.insert_tracked(offset, &continuation);
849
849
+
doc.cursor.write().offset = offset + len;
850
850
+
tracing::debug!("continuation {}", continuation);
851
851
+
}
852
852
+
} else {
853
853
+
// Normal soft break: insert newline + zero-width char for cursor positioning.
854
854
+
let _ = doc.insert_tracked(offset, "\n\u{200C}");
855
855
+
doc.cursor.write().offset = offset + 2;
856
856
+
}
857
857
+
} else {
858
858
+
// Replace zero-width char with newline
859
859
+
let _ = doc.replace_tracked(offset - 1, 1, "\n");
860
860
+
doc.cursor.write().offset = offset;
822
861
}
823
862
824
863
doc.selection.set(None);
···
829
868
let range = range.normalize();
830
869
doc.pending_snap.set(Some(SnapDirection::Forward));
831
870
871
871
+
let cursor_offset = range.start;
832
872
if !range.is_caret() {
833
833
-
let _ = doc.remove_tracked(range.start, range.len());
873
873
+
let _ = doc.remove_tracked(cursor_offset, range.len());
834
874
}
835
835
-
836
836
-
let cursor_offset = range.start;
837
875
838
876
// Check for list context
839
877
if let Some(ctx) = detect_list_context(doc.loro_text(), cursor_offset) {
+1
-1
crates/weaver-renderer/src/css.rs
···
148
148
149
149
/* Lists */
150
150
ul, ol {{
151
151
-
margin-left: 2rem;
151
151
+
margin-left: 1rem;
152
152
margin-bottom: 1rem;
153
153
}}
154
154