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